more
159
PATCH_NOTES.md
|
|
@ -1,148 +1,19 @@
|
|||
## 15.15.0
|
||||
# Patch notes
|
||||
|
||||
- 巨大ファイル分割を実施。
|
||||
- `world.js` をコアクラス、視点/家系/戦闘/環境/空間管理/アリ/更新/配置ログ/ブートストラップに分割。
|
||||
- `tarinai.js` をコアクラス、人格・関係、病気・巣箱、欲求・アイテム、社会/移動/寿命、描画に分割。
|
||||
- `ui.js` を状態、ログ、選択詳細、レイアウト/ダイアログ、道具、イベント束縛に分割。
|
||||
- `ui_family.js` をデータ、非同期構築、レイアウト、パス生成、描画に分割。
|
||||
- 既存グローバルAPI互換を維持し、挙動変更を避ける形で分割。
|
||||
- Service Workerキャッシュと静的アセットバージョンを `15.15.0` に更新。
|
||||
## 15.20.3
|
||||
|
||||
- Removed item-effect speech bubbles: `ドン`, `ぐるる`, `すっきり`, `Hg xN`, `ちまっ`, `でかっ`, `へにゃ`, `ムキッ`, and `mm...`.
|
||||
- Reorganized the tool palette into color-coded collapsible groups: operation, food/water/grass, medicine/effects, habitat/living things, hazards/obstacles.
|
||||
- Changed the tool UI ball icon to a soccer-ball emoji preview.
|
||||
- Changed time phases: morning 05:00, day 10:00, evening 17:00, night 20:00.
|
||||
- Increased genkotsu knockback for Tarinai and ants; genkotsu now also blasts zunchi away.
|
||||
- Genkotsu damage now adds fear/stress and decreases Tarinai openness.
|
||||
- Rain weather overlay is now diagonal streaks. Fallen rain-water items, zunda juice, and mercury remain horizontal oval item sprites.
|
||||
- Tightened mobile operation UI visibility to smartphone-class compact touch devices.
|
||||
- Sleep-disease Tarinai can be moved by ant hauling without snapping back to their sleep anchor.
|
||||
|
||||
## 15.9.1 修正
|
||||
- 草を地面レイヤーとして扱い、既存の草の上にもオブジェクトを配置できるようにしました。草同士・草と障害物の植え付け制限は維持しています。
|
||||
- けんか餅のゲーム内描画から星印を削除し、×印のみ表示するようにしました。
|
||||
- ダメージ後に出る「こわい…」吹き出しを削除しました。
|
||||
- 家系図に「更新」トグルを追加しました。デフォルトはOFFで、ONの間だけ家系図を描画・更新します。
|
||||
- 死亡した個体のライブIDを再利用しないようにし、友達・敵対関係が別個体へ引き継がれて見える問題を防止しました。死亡時の一時ターゲット参照も掃除します。
|
||||
- たりないデータUIをカテゴリ別の折り畳み表示に変更しました。
|
||||
## 15.20.2 and earlier summary
|
||||
|
||||
# Patch Notes
|
||||
|
||||
## Encoding cleanup
|
||||
- Normalized `index.html` back to UTF-8.
|
||||
- Replaced mojibake strings with the intended labels.
|
||||
- Escaped Japanese text in source files to avoid encoding-dependent corruption.
|
||||
|
||||
## Personality specification fixes
|
||||
- Personality tags and data UI now use Japanese labels from the four-axis personality model.
|
||||
- Threshold-crossing personality logs are now generated in Japanese and only when visible trait bands change.
|
||||
- Daily personality caps now track increase and decrease separately per parameter.
|
||||
- Current personality effects are neutral inside the -0.5 to +0.5 band.
|
||||
- Direct conversion to the legacy timid personality after damage was removed; cowardly behavior is derived from high neuroticism plus low aggression.
|
||||
- Corpse/splat contact now increases stress and slowly reduces neuroticism through habituation.
|
||||
|
||||
## Follow-up fixes
|
||||
- Fixed CSS emoji escapes for tool UI icons. CSS now uses code-point escapes such as `\\1F448` instead of invalid surrogate escapes.
|
||||
- Hid the detailed four-axis personality rows from the compact tarinai data UI.
|
||||
- Restored `bestBedFor()` fallback so tarinai can choose hay beds when no usable nest box is available.
|
||||
- Damage now wakes sleeping tarinai and forces tarinai hidden inside a nest box to leave the box.
|
||||
- Added a short minimum stay window after entering a nest box to avoid immediate exit loops.
|
||||
- Renamed the friend relation label from `\\u4ef2\\u826f\\u3057` to `\\u53cb\\u9054` in UI text and related logs.
|
||||
|
||||
|
||||
## Personality cleanup v3
|
||||
- Current live friend count now changes sociability drift. Family/friend proximity raises sociability more when the tarinai has more current friends, while a lonely tarinai with no current friends can slowly lose sociability.
|
||||
- Removed the legacy named personality route from constructor, serialization, child inheritance, selected data UI, selected-card overlay, and family-tree titles. The game now uses only birthPersonality/currentPersonality four-axis data.
|
||||
- Kept empty legacy stubs only for old-save compatibility so older saves cannot revive the pre-four-axis personality model.
|
||||
|
||||
## Follow-up fixes v4
|
||||
- Restored compact personality information in the tarinai data panel: visible tags, birth personality, and current personality.
|
||||
- Moved the focused-tarinai highlight outside the sprite rotation transform so the ring and label stay upright.
|
||||
- Shortened ecology cards and added the HP/stress meter placement note: upper meter is HP, lower meter is stress.
|
||||
- Raised the friendship threshold to 16 affinity and reused the same threshold for friend count, friend behavior, friend UI, and observe highlights.
|
||||
- Observe mode now highlights friends of the selected tarinai as well as parents, children, and enemies.
|
||||
|
||||
## Ant nest update
|
||||
- Added a new placeable `ant_nest` object and tool button.
|
||||
- Added ant sprites cropped from the provided source image: queen, worker variants, and upside-down worker corpse.
|
||||
- Each ant nest starts with 6 workers, caps at 10 workers, and sends at most 3 workers outside at once.
|
||||
- Worker ants spawn above their home nest, random-walk, search for tarinai, and drag found tarinai back to the nest. Multiple workers dragging the same tarinai increase carrying speed.
|
||||
- Dragged tarinai struggle automatically and can kill workers. Dead workers become edible `ant_corpse` items and slightly reduce hygiene.
|
||||
- If a nest below capacity brings back a tarinai, its worker count increases by 1.
|
||||
- If a full nest brings back another tarinai, a queen ant appears and creates a new ant nest in an open spot.
|
||||
- Ant health does not recover.
|
||||
- Added the requested Irasutoya credit entry.
|
||||
|
||||
## Ant nest follow-up v5
|
||||
- Increased ant movement and hauling constants by 1.5x from the previous slow tuning.
|
||||
- Kept queen spawning to at most one queen per nest per in-game day, while making spawned queens keep retrying until a new ant nest is built somewhere.
|
||||
- Reduced the ant nest object radius from 34 to 17.
|
||||
- Removed the separate ant corpse sprite asset. Ant corpses now render by flipping the worker ant sprite vertically and remain stationary while decaying.
|
||||
- Added shared ant damage handling for firecracker explosions and dropped-object impacts.
|
||||
- Kept ant worker HP in the nest worker pool so damaged workers remain damaged after returning to and leaving the nest.
|
||||
|
||||
|
||||
## 15.11.0
|
||||
- イベントUI下部にログ種別別のプッシュ通知設定を追加。ON時は左下最前面に通知表示
|
||||
- 巣がなくなったアリは空きのある別巣へ所属変更。空き巣がない場合は死亡せず彷徨う
|
||||
- 女王アリを大きめに表示
|
||||
- 画像アセットをWebP化し、参照をWebPへ切り替え
|
||||
- Service Workerによる静的アセットキャッシュを追加し、indexのno-cache指定を解除
|
||||
|
||||
## 15.12.0
|
||||
- 優先度S/A/B相当の効果音イベントを追加。未指定音源はWeb Audioの仮SEで再生。
|
||||
- 効果音カテゴリを「たりないの声」「通知」「操作」に分類し、イベントUI下部から個別ON/OFF可能にした。
|
||||
- 音素材差し替え用の安定IDを `docs/SOUND_IDS.md` に整理。
|
||||
- げんこつ、爆竹、喧嘩、ダメージ、死亡、誕生、アリ、病気、回復、水、草、zunchi、UI操作などに音フックを追加。
|
||||
|
||||
|
||||
## 15.12.3
|
||||
- プッシュ通知の保存キーを更新し、初期状態はOFFに戻した。
|
||||
- 効果音設定は右上の音設定で、操作音・通知音のみデフォルトON、たりないの声はデフォルトOFFに変更。
|
||||
- 操作音と通知音の内蔵仮SE音量を大幅に増加。
|
||||
- つまむ操作でたりないデータカードが開く挙動を修正。
|
||||
- 道具から配置する全オブジェクトで配置音が鳴るよう、placeItem側に配置音を統合。
|
||||
- イベントUI折り畳み時の高さを圧縮。
|
||||
|
||||
|
||||
## 15.12.4
|
||||
- 効果音の仮SE音量を再度引き上げ。
|
||||
- イベントUI内の効果音設定を廃止し、右上の音設定メニューへ統一。
|
||||
- イベントログ欄が上下端に達した時、右メニュー全体へスクロールを受け渡すよう修正。
|
||||
|
||||
## 15.15.0
|
||||
- バージョン/キャッシュ管理を `js/version.js` と `service-worker.js` に統一。
|
||||
- Service Worker登録を `updateViaCache: "none"` に変更し、`.htaccess` でService Worker本体をno-cache化。
|
||||
- `js/event_bus.js` を追加し、ログ/フェーズ/配置/地形dirty/FPS変化などをイベント化。
|
||||
- `js/performance_manager.js` を追加し、FPSレベル、アリ更新間引き、エフェクト上限、統計更新間隔、地形再描画間隔を中央管理。
|
||||
- 地面キャッシュを全走査ハッシュ方式から `terrainVersion` dirty方式へ変更。
|
||||
- SpatialGridをアリ/障害物/食料/危険物セルに拡張。
|
||||
- `?debug=1` の内部デバッグオーバーレイを追加。
|
||||
- 音システムに音ID別ゲイン、音ID別最小間隔、同時発音上限、サンプル読み込み失敗イベントを追加。
|
||||
|
||||
## v15.15.0
|
||||
- 個体パラメータ変化アイテムを追加: 下剤、プロテイン、ンイテロプ、弾薬、怪しい薬。
|
||||
- 下剤: 効果時間中3秒ごとにずんちを放出し、食事で空腹度が下がらない。
|
||||
- プロテイン/ンイテロプ: 永続の与ダメージ・病気発症確率補正を追加。両者は排他で、最後に食べたものだけ有効。
|
||||
- 弾薬: 効果時間中の歩行速度10倍と、移動中のたりない/アリ衝突ノックバックを追加。
|
||||
- 怪しい薬: 行動変容アイテム効果と病気の中からランダム2種を付与。
|
||||
- たりないデータに「現在のアイテム効果」を追加。
|
||||
- バージョンとService Workerキャッシュを15.15.0に更新。
|
||||
|
||||
## v15.18.0
|
||||
- Implemented static `app_manifest.json` and `scripts/generate_app_files.py` for HTML/Service Worker static-list synchronization.
|
||||
- Split `css/styles.css` into ordered CSS files while keeping a compatibility shim.
|
||||
- Added `js/effect_registry.js` and routed item-effect labels/colors/random pools/selected display text through the registry.
|
||||
- Added `js/sound_pack.js`; `audio.js` now consumes sound pack gains, cooldowns, and sample paths.
|
||||
- Added mobile canvas input modes: 自動 / カメラ / 道具 / 家系図.
|
||||
- Expanded debug overlay with Service Worker/cache/audio/effect diagnostics.
|
||||
- Added dedicated tool icons for 下剤, プロテイン, ンイテロプ, 弾薬, 怪しい薬, 水銀, 巨大薬, 矮小薬, ずんだ汁.
|
||||
|
||||
## v15.19.1
|
||||
- 効果音のマスター出力、カテゴリ別ゲイン、ID別ゲインを再調整。連続再生で小さくなりにくいよう同時発音上限とWeb Audio出力経路を変更。
|
||||
- 食べ物の自然消耗を連続減少から半日に2回程度の離散消耗へ変更。衛生統計ペナルティも同じタイミングに抑制。
|
||||
- 薬系アイテムの地面表示とUIアイコンを錠剤風に変更。
|
||||
- プロテイン/ンイテロプの外観を山盛りの粉に変更。永続効果中は継続的なオーラ/粒子エフェクトを表示。
|
||||
- ずんだ汁を緑の水滴外観へ変更。雨と水滴表示は灰色寄りへ変更。
|
||||
- バージョンとService Workerキャッシュを 15.19.1 に更新。
|
||||
|
||||
|
||||
## v15.19.1
|
||||
- Removed embedded letters/sub-labels from new item sprites and tool icons.
|
||||
- Refreshed parameter item UI icons; cache-busted updated icon URLs.
|
||||
- Fixed firecracker placement triggering explosion sound through accident log inference.
|
||||
- Made giant drug placed sprite larger and dwarf drug placed sprite smaller.
|
||||
- Redrew mercury, zunda juice, and water as rain-drop style sprites.
|
||||
- Changed protein/niteropu persistent visuals to red rising particles and purple falling particles.
|
||||
- Fixed remaining colony chart tab label from 生活 to 精神.
|
||||
- Consolidated documentation into dense English files; left `docs/prompt.txt` untouched.
|
||||
- Restored rain, revised water/zunda/mercury item sprites, added zunchi-slave head zunchi, fight win rate, fight defeat chance, and post-wake lock.
|
||||
- Earlier patches added parameter items, persistent item effects, WebP assets, sound categories, push notifications, ant systems, family tree UI, performance manager, event bus, manifest generation, CSS split, and large-file JS split.
|
||||
|
|
|
|||
171
README.md
|
|
@ -1,156 +1,35 @@
|
|||
# Tarinai Colony Observation Game
|
||||
|
||||
Single-page, dependency-free browser simulation. Open `index.html`; no build step, package manager, server API, transpiler, or module loader is required. Runtime is plain HTML/CSS/JS loaded by ordered `<script>` tags.
|
||||
Buildless single-page browser simulation. Open `index.html`; no package manager, transpiler, server API, or module loader is required. Runtime order is explicit `<script defer>` order generated from `app_manifest.json`.
|
||||
|
||||
## Codex operating model
|
||||
## Edit contract
|
||||
|
||||
Read only these first: `README.md`, `docs/AI_CONTEXT.md`, `docs/code_map.md`. Then open the smallest implementation file named by the map. Avoid broad grep unless a symbol is not mapped. Keep Japanese UI strings escaped in source (`\uXXXX` in JS, entities in HTML/Markdown/CSS where applicable). After edits run `node --check js/*.js` and verify `index.html` script order.
|
||||
- Preserve Japanese in-game UI tone and strings.
|
||||
- Do not edit `docs/prompt.txt`; it is external user material.
|
||||
- Use `app_manifest.json` as the source of truth for CSS/JS load order and service-worker cache entries.
|
||||
- Run `python3 scripts/generate_app_files.py` after version or asset-list changes.
|
||||
- Run `node --check js/*.js service-worker.js` before zipping.
|
||||
- Verify ZIP integrity with `zip -T`.
|
||||
|
||||
## Core simulation
|
||||
## Documentation map
|
||||
|
||||
- Entities: `Tarinai` agents with hunger, sleep/energy, loneliness, stress, health, age, personality, family links, relationships, diseases, favorite flag, target/need AI, transient animation timers.
|
||||
- World: bounded field with weather, time, lighting, temperature, spatial-grid queries, items, effects, logs, archive/family data, placement and collision rules.
|
||||
- Observation loop: agents autonomously seek food/water/sleep/social targets, react to zunchi, tools, rain, cold, crowding, relationships, disease, damage, conflict, reproduction, and player input.
|
||||
- Records: observation log and family tree store births, deaths, parentage, cause-of-death labels, favorite state, and key state changes.
|
||||
- `docs/ARCHITECTURE.md`: runtime architecture, file ownership, system boundaries.
|
||||
- `docs/CONTENT_GUIDE.md`: item visuals, UI/field visual parity, audio, bubble text inventory.
|
||||
- `docs/MAINTENANCE.md`: release process, validation, debugging, cache/version rules.
|
||||
- `docs/prompt.txt`: untouched user prompt/context archive.
|
||||
|
||||
## Important mechanics
|
||||
## Core systems
|
||||
|
||||
- Personality uses four axes: aggression, openness, sociability, and neuroticism. Children inherit from the parents' current four-axis personality average with variation.
|
||||
- Relationships track affinity/fear/fight outcomes and influence approach, avoidance, intimidation, conflict, co-parent behavior, and UI summaries.
|
||||
- Death causes are normalized by `js/health.js`: fight, hunger, disease, stress overload, over-poking, firecracker, accident, lifespan. Major damage within 10 seconds can become `<cause> de suyaku` in UI text (`...\u3067\u8870\u5f31`).
|
||||
- Water-curable diseases force high-priority water seeking. Zunda-curable diseases force high-priority zunda seeking.
|
||||
- Favorite agents show a star above the sprite and can be toggled from the observation panel.
|
||||
- `World`: `js/world.js` plus `js/world_*.js`; time, weather, placement, effects, family records, spatial queries, ants, combat, logging.
|
||||
- `Tarinai`: `js/tarinai.js` plus `js/tarinai_*.js`; identity, personality, needs, items, disease, movement, social behavior, rendering.
|
||||
- `Item`: `js/items.js`; lifecycle, scaling, field rendering. Field rendering is the visual source of truth for tool icons.
|
||||
- `EffectRegistry`: `js/effect_registry.js`; item-effect names, exclusivity, display metadata, random-effect candidates.
|
||||
- `Audio`: `js/audio.js` + `js/sound_pack.js`; category toggles, synthetic fallbacks, sample paths, per-ID volume/cooldown.
|
||||
- `Performance`: `js/performance_manager.js`; FPS quality level, ant stride, effect budget, terrain redraw cadence.
|
||||
|
||||
## Diseases
|
||||
## Current high-risk areas
|
||||
|
||||
- Zunchi disease: zunchi-derived contamination; slows movement, increases stress, emits miasma effect, cured by water.
|
||||
- Sleep disease: caused by sleep drug; stops all behavior and sleeps indefinitely; cured by damage.
|
||||
- Explosion disease: low-probability result of explosion knockback; explodes after half a day; cured by water or zunda mochi.
|
||||
- Fight-wound disease: low-probability result after bleeding for five in-game hours; spins and wanders aimlessly; cured by zunda mochi.
|
||||
|
||||
## Tools and items
|
||||
|
||||
- Direct tools: observe, delete, poke, pinch, spawn.
|
||||
- Food/medicine: zunchi, zunda mochi, heko mochi, kenka mochi, water, water bowl, sleep drug, grass.
|
||||
- Environment/objects: stone, hay bed, nest box, cleaning tool, ball, firecracker, vertical fence, horizontal fence.
|
||||
- Cleaning removes zunchi/blood/traces while pushing agents and applying water effects. Water bowl is persistent water. Nest box lowers stress nearby and improves sleep quality. Rain increases falling water items.
|
||||
|
||||
## File map
|
||||
|
||||
```text
|
||||
index.html Static DOM shell; ordered script tags; no long ecology/body text.
|
||||
css/styles.css Layout, HUD, dialogs, tooltips, responsive rules.
|
||||
js/data.js Sprite/object assets, metrics, config, field presets.
|
||||
js/text_catalog.js Escaped Japanese UI catalog: tooltips and ecology cards.
|
||||
js/math.js Generic math helpers.
|
||||
js/assets.js Lazy image loading, caches, fallback render images.
|
||||
js/audio.js WebAudio/sample effects.
|
||||
js/render.js Background, weather, lighting, HUD, global canvas render.
|
||||
js/sim_core.js Names, personalities, relationships, effects, spatial grid.
|
||||
js/health.js Damage memory, death-cause normalization, damage application.
|
||||
js/items.js Item state, lifecycle, drawing, grass/zunchi/ball/firecracker logic.
|
||||
js/tarinai.js Tarinai constructor/core state only.
|
||||
js/tarinai_identity_social.js Agent identity, records, personality, relations, stress/damage wrappers.
|
||||
js/tarinai_disease_nest.js Diseases, water effects, nest-box entry/exit.
|
||||
js/tarinai_needs_items.js Needs AI, eating/drinking, item interaction, facing, zunchi behavior.
|
||||
js/tarinai_social_move_life.js Social interaction, movement, life/death.
|
||||
js/tarinai_render.js Agent drawing and hit testing.
|
||||
js/world.js World constructor/core state only.
|
||||
js/world_view.js Field scale, camera, viewport, tool sizing, time/phase helpers.
|
||||
js/world_family_social.js Family tree, birth, fights, social conflict entry points.
|
||||
js/world_combat_effects.js Damage, explosions, water hose, effects, balls, collision outcomes.
|
||||
js/world_environment.js Obstacles, fences, grass placement, temperature, nearest queries.
|
||||
js/world_spatial_budget.js Spatial grid, draw list, budgets, compaction, performance helpers.
|
||||
js/world_ants_system.js Ant updates, nest haul/founding, queen spawning.
|
||||
js/world_update.js Main world update, weather, out-of-bounds sanitation, pointer info.
|
||||
js/world_placement_log.js Placement, click handling, log classification/dispatch.
|
||||
js/world_bootstrap.js Creates global `world` after World mixins load.
|
||||
js/ui.js UI state/cache references only.
|
||||
js/ui_log.js Log rendering, push notification controls, sound setting sync.
|
||||
js/ui_selected.js Selected Tarinai data panel and text formatting.
|
||||
js/ui_layout_dialogs.js Canvas resize, dialogs, field switching, top buttons.
|
||||
js/ui_tools.js Tool size badges and tool size cycling.
|
||||
js/ui_bind.js Event listeners and input binding.
|
||||
js/ui_charts.js Population/statistics charts.
|
||||
js/ui_family*.js Family/archive data, async build, layout, paths, render.
|
||||
docs/AI_CONTEXT.md Minimal implementation context for AI edits.
|
||||
docs/code_map.md Symbol-to-file lookup table.
|
||||
```
|
||||
|
||||
## Edit rules
|
||||
|
||||
- Preserve zero-build script order. Add new JS files explicitly in `index.html` before dependents.
|
||||
- Keep global symbols intentional; this codebase is not ESM.
|
||||
- Prefer catalog/config/helper extraction over adding large inline blocks to `index.html` or `tarinai.js`.
|
||||
- Do not reintroduce removed compatibility files (`sim.js`, `ui_exports.js`) or broad JS/CSS caching for active code.
|
||||
- For public uploads, delete old server files before replacing the directory, then upload the full new ZIP contents.
|
||||
|
||||
|
||||
## v15 notes
|
||||
- Colony chart tab `events` was removed. `environment` now reports security, hygiene, and happiness.
|
||||
- Selected Tarinai UI includes per-individual records attached by structured observation events.
|
||||
- Observation highlights selected relatives and major social relations: parent, child, friend, enemy.
|
||||
- Explosions impart velocity to nearby zunchi items; cursor contact reduces stress and emits dense heart effects.
|
||||
|
||||
## v4 \u8ffd\u52a0\u4fee\u6b63
|
||||
- \u5de3\u7bb1\u5165\u9000\u5ba4\u3092\u5c02\u7528\u72b6\u614b\u5316\u3057\u3001\u900f\u660e\u5316\u30fb\u5f53\u305f\u308a\u5224\u5b9a\u6d88\u6ec5\u30fb\u500b\u4f53\u6570\u53cd\u6620\u30fb\u9000\u51fa\u5fa9\u5e30\u3092\u5b89\u5b9a\u5316\u3002
|
||||
- \u7761\u7720\u6642\u306e\u512a\u5148\u9806\u4f4d\u3092\u300c\u5de3\u7bb1 \u2192 \u5e72\u3057\u8349\u30d9\u30c3\u30c9 \u2192 \u305d\u306e\u5834\u300d\u306b\u5909\u66f4\u3002
|
||||
- \u305f\u308a\u306a\u3044\u30c7\u30fc\u30bfUI\u306e\u53cb\u9054/\u6575\u5bfe\u3092\u5168\u4ef6\u30c1\u30c3\u30d7\u8868\u793a\u306b\u3057\u3001\u30af\u30ea\u30c3\u30af\u3067\u500b\u4f53\u3078\u79fb\u52d5\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3002
|
||||
|
||||
|
||||
## 15.18.0
|
||||
- イベントUI下部にログ種別別のプッシュ通知設定を追加。ON時は左下最前面に通知表示
|
||||
- 巣がなくなったアリは空きのある別巣へ所属変更。空き巣がない場合は死亡せず彷徨う
|
||||
- 女王アリを大きめに表示
|
||||
- 画像アセットをWebP化し、参照をWebPへ切り替え
|
||||
- Service Workerによる静的アセットキャッシュを追加し、indexのno-cache指定を解除
|
||||
|
||||
### 効果音差し替え
|
||||
v15.18.0以降、`js/audio.js` の `samplePaths` に `sfx_genkotsu_impact` などの音IDとファイルパスを追加すると、内蔵の仮SEよりその音源が優先されます。ID一覧は `docs/SOUND_IDS.md` を参照してください。
|
||||
|
||||
|
||||
## 15.18.0
|
||||
- プッシュ通知の保存キーを更新し、初期状態はOFFに戻した。
|
||||
- 効果音設定は右上の音設定で、操作音・通知音のみデフォルトON、たりないの声はデフォルトOFFに変更。
|
||||
- 操作音と通知音の内蔵仮SE音量を大幅に増加。
|
||||
- つまむ操作でたりないデータカードが開く挙動を修正。
|
||||
- 道具から配置する全オブジェクトで配置音が鳴るよう、placeItem側に配置音を統合。
|
||||
- イベントUI折り畳み時の高さを圧縮。
|
||||
|
||||
|
||||
## 15.18.0
|
||||
- 効果音の仮SE音量を再度引き上げ。
|
||||
- イベントUI内の効果音設定を廃止し、右上の音設定メニューへ統一。
|
||||
- イベントログ欄が上下端に達した時、右メニュー全体へスクロールを受け渡すよう修正。
|
||||
|
||||
## 15.18.0 内部システム更新
|
||||
- バージョン/キャッシュ管理、イベントバス、PerformanceManager、地形dirtyキャッシュ、拡張SpatialGrid、デバッグモードを追加しました。
|
||||
- `?debug=1` をURLに付けるとFPS/負荷レベル/エンティティ数/イベント履歴を確認できます。
|
||||
- 詳細は `docs/INTERNAL_SYSTEMS.md` を参照してください。
|
||||
|
||||
### v15.18.0 個体効果アイテム
|
||||
- 下剤、プロテイン、ンイテロプ、弾薬、怪しい薬を追加。
|
||||
- たりないデータの「現在のアイテム効果」で、永続効果と残り時間つき効果を確認できます。
|
||||
|
||||
### v15.18.0 internal maintenance
|
||||
- Static loading order is centralized in `app_manifest.json`.
|
||||
- Run `python3 scripts/generate_app_files.py` after adding/removing CSS or JS files.
|
||||
- Sound replacement is centralized in `js/sound_pack.js`.
|
||||
- Item/disease effect metadata is centralized in `js/effect_registry.js`.
|
||||
|
||||
## v15.19.1
|
||||
- 効果音のマスター出力、カテゴリ別ゲイン、ID別ゲインを再調整。連続再生で小さくなりにくいよう同時発音上限とWeb Audio出力経路を変更。
|
||||
- 食べ物の自然消耗を連続減少から半日に2回程度の離散消耗へ変更。衛生統計ペナルティも同じタイミングに抑制。
|
||||
- 薬系アイテムの地面表示とUIアイコンを錠剤風に変更。
|
||||
- プロテイン/ンイテロプの外観を山盛りの粉に変更。永続効果中は継続的なオーラ/粒子エフェクトを表示。
|
||||
- ずんだ汁を緑の水滴外観へ変更。雨と水滴表示は灰色寄りへ変更。
|
||||
- バージョンとService Workerキャッシュを 15.19.1 に更新。
|
||||
|
||||
|
||||
### v15.19.1
|
||||
- 新アイテム系スプライト/道具アイコンから文字表示を削除。
|
||||
- 爆竹配置時に爆発音が鳴る問題を修正。
|
||||
- 巨大薬/矮小薬の設置サイズを視覚的に差別化。
|
||||
- 水銀/ずんだ汁/水滴を降雨の水滴表現に合わせて再描画。
|
||||
- プロテイン/ンイテロプの永続演出を粒子表現へ変更。
|
||||
- 統計タブの「生活」表記残りを「精神」に修正。
|
||||
- Item effects touch `effect_registry`, `tarinai_item_effects`, `tarinai_needs_items`, `items`, and selected-info UI.
|
||||
- Mobile input must remain hidden on desktop-class devices; use body class `touch-mobile-ui` only for compact no-hover coarse-pointer devices.
|
||||
- Weather rain overlay is diagonal line streaks. Fallen water items, zunda juice, and mercury share the same horizontal oval item-droplet silhouette; recolor only.
|
||||
- Tool UI icons must be generated previews of `js/items.js` drawing helpers, never separate designs.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "15.19.1",
|
||||
"version": "15.20.3",
|
||||
"css": [
|
||||
"css/base.css",
|
||||
"css/layout.css",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 338 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 280 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 324 B |
|
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 278 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 280 B |
20
css/base.css
|
|
@ -399,13 +399,13 @@ h1 {
|
|||
.tool[data-tool="fight_mochi"] { --tool-icon: url("../assets/ui/tool_fight_mochi.webp"); }
|
||||
.tool[data-tool="water_bowl"] { --tool-icon: url("../assets/ui/tool_water_bowl.webp"); }
|
||||
|
||||
/* v15.19.1: updated parameter item tool icons */
|
||||
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.19.1"); }
|
||||
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.19.1"); }
|
||||
.tool[data-tool="niteropu"] { --tool-icon: url("../assets/ui/tool_niteropu.webp?v=15.19.1"); }
|
||||
.tool[data-tool="ammo"] { --tool-icon: url("../assets/ui/tool_ammo.webp?v=15.19.1"); }
|
||||
.tool[data-tool="mystery_drug"] { --tool-icon: url("../assets/ui/tool_mystery_drug.webp?v=15.19.1"); }
|
||||
.tool[data-tool="mercury"] { --tool-icon: url("../assets/ui/tool_mercury.webp?v=15.19.1"); }
|
||||
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.19.1"); }
|
||||
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.19.1"); }
|
||||
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.19.1"); }
|
||||
/* v15.20.3: updated parameter item tool icons */
|
||||
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.20.3"); }
|
||||
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.20.3"); }
|
||||
.tool[data-tool="niteropu"] { --tool-icon: url("../assets/ui/tool_niteropu.webp?v=15.20.3"); }
|
||||
.tool[data-tool="ammo"] { --tool-icon: url("../assets/ui/tool_ammo.webp?v=15.20.3"); }
|
||||
.tool[data-tool="mystery_drug"] { --tool-icon: url("../assets/ui/tool_mystery_drug.webp?v=15.20.3"); }
|
||||
.tool[data-tool="mercury"] { --tool-icon: url("../assets/ui/tool_mercury.webp?v=15.20.3"); }
|
||||
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.20.3"); }
|
||||
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.20.3"); }
|
||||
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.20.3"); }
|
||||
|
|
|
|||
|
|
@ -364,3 +364,27 @@ meter {
|
|||
border: 1px solid rgba(84,68,48,0.10);
|
||||
}
|
||||
|
||||
|
||||
/* v15.20.3: clearer tool category grouping. */
|
||||
.tool-category[data-tool-category="operate"] { --tool-cat-a: rgba(224,239,255,0.82); --tool-cat-b: rgba(203,226,255,0.74); --tool-cat-line: rgba(82,128,190,0.26); }
|
||||
.tool-category[data-tool-category="food"] { --tool-cat-a: rgba(236,252,219,0.82); --tool-cat-b: rgba(214,244,194,0.74); --tool-cat-line: rgba(96,156,72,0.26); }
|
||||
.tool-category[data-tool-category="medicine"] { --tool-cat-a: rgba(243,231,255,0.84); --tool-cat-b: rgba(230,214,252,0.78); --tool-cat-line: rgba(136,94,184,0.28); }
|
||||
.tool-category[data-tool-category="habitat"] { --tool-cat-a: rgba(255,245,218,0.84); --tool-cat-b: rgba(250,229,186,0.76); --tool-cat-line: rgba(174,125,54,0.28); }
|
||||
.tool-category[data-tool-category="hazard"] { --tool-cat-a: rgba(255,230,224,0.84); --tool-cat-b: rgba(250,207,197,0.78); --tool-cat-line: rgba(189,88,66,0.30); }
|
||||
.tool-category { border-color: var(--tool-cat-line, rgba(84,68,48,0.10)); background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,255,255,0.36)), rgba(255,255,255,0.28)); }
|
||||
.tool-category-toggle { background: linear-gradient(180deg, var(--tool-cat-a, rgba(255,252,244,0.66)), var(--tool-cat-b, rgba(255,252,244,0.54))); border-bottom-color: var(--tool-cat-line, rgba(84,68,48,0.08)); }
|
||||
.tool[data-tool="ball"] { --tool-icon: none; }
|
||||
.tool[data-tool="ball"]::before {
|
||||
content: "⚽";
|
||||
background: none;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
opacity: 1;
|
||||
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
|
||||
}
|
||||
.tool[data-tool="ball"]::after { background: none; }
|
||||
@media (min-width: 921px) {
|
||||
.mobile-mode-controls { display: none !important; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,15 +180,15 @@
|
|||
}
|
||||
|
||||
/* v15.18.0: item icons and mobile input mode */
|
||||
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.19.1"); }
|
||||
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.19.1"); }
|
||||
.tool[data-tool="niteropu"] { --tool-icon: url("../assets/ui/tool_niteropu.webp?v=15.19.1"); }
|
||||
.tool[data-tool="ammo"] { --tool-icon: url("../assets/ui/tool_ammo.webp?v=15.19.1"); }
|
||||
.tool[data-tool="mystery_drug"] { --tool-icon: url("../assets/ui/tool_mystery_drug.webp?v=15.19.1"); }
|
||||
.tool[data-tool="mercury"] { --tool-icon: url("../assets/ui/tool_mercury.webp?v=15.19.1"); }
|
||||
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.19.1"); }
|
||||
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.19.1"); }
|
||||
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.19.1"); }
|
||||
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.20.3"); }
|
||||
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.20.3"); }
|
||||
.tool[data-tool="niteropu"] { --tool-icon: url("../assets/ui/tool_niteropu.webp?v=15.20.3"); }
|
||||
.tool[data-tool="ammo"] { --tool-icon: url("../assets/ui/tool_ammo.webp?v=15.20.3"); }
|
||||
.tool[data-tool="mystery_drug"] { --tool-icon: url("../assets/ui/tool_mystery_drug.webp?v=15.20.3"); }
|
||||
.tool[data-tool="mercury"] { --tool-icon: url("../assets/ui/tool_mercury.webp?v=15.20.3"); }
|
||||
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.20.3"); }
|
||||
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.20.3"); }
|
||||
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.20.3"); }
|
||||
.mobile-mode-controls {
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
|
@ -218,9 +218,8 @@
|
|||
background: linear-gradient(180deg, #ffe9a8, #ffd376);
|
||||
box-shadow: 0 3px 10px rgba(150, 105, 30, .20);
|
||||
}
|
||||
@media (pointer: coarse), (max-width: 760px) {
|
||||
.mobile-mode-controls { display: flex; }
|
||||
body.mobile-mode-camera #gameCanvas { cursor: grab; }
|
||||
body.mobile-mode-tool #gameCanvas { cursor: crosshair; }
|
||||
body.mobile-mode-family .lineage-main-card { z-index: 9; }
|
||||
}
|
||||
body.touch-mobile-ui .mobile-mode-controls:not([hidden]) { display: flex; }
|
||||
body:not(.touch-mobile-ui) .mobile-mode-controls { display: none !important; }
|
||||
body.touch-mobile-ui.mobile-mode-camera #gameCanvas { cursor: grab; }
|
||||
body.touch-mobile-ui.mobile-mode-tool #gameCanvas { cursor: crosshair; }
|
||||
body.touch-mobile-ui.mobile-mode-family .lineage-main-card { z-index: 9; }
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
# AI_CONTEXT
|
||||
|
||||
Purpose: dense orientation for code-editing agents. Browser-only simulation; no build system. Source uses globals and strict script order, not ESM.
|
||||
|
||||
Load order from `index.html`: `version -> event_bus -> performance_manager -> data -> math -> assets -> audio -> render -> sim_core -> items -> ants -> health -> tarinai core/mixins -> world core/mixins -> world_bootstrap -> text_catalog -> ui state/helpers/bind -> ui_charts -> ui_family modules -> main -> debug_tools`.
|
||||
|
||||
Primary objects:
|
||||
- `World` (`js/world.js` + `js/world_*.js`): constructor is in `world.js`; methods are split by concern into `world_view`, `world_family_social`, `world_combat_effects`, `world_environment`, `world_spatial_budget`, `world_ants_system`, `world_update`, and `world_placement_log`.
|
||||
- `Tarinai` (`js/tarinai.js` + `js/tarinai_*.js`): constructor is in `tarinai.js`; methods are split into identity/social, disease/nest, needs/items, social/move/life, and render modules.
|
||||
- `Item` (`js/items.js`): item state, draw, lifecycle.
|
||||
- `HEALTH` (`js/health.js`): damage memory, major-damage window, death-cause classification, damage application.
|
||||
- `TEXT_CATALOG` (`js/text_catalog.js`): Japanese tooltips and ecology cards. Keep Japanese escaped.
|
||||
|
||||
Behavior hotspots:
|
||||
- Need selection / target priority: `Tarinai.resolveNeeds` and related target helpers.
|
||||
- Disease state: constructor fields plus `infect*`, `recover*`, `updateZunchiDisease`, `updateSpecialDiseases`.
|
||||
- Conflict: intimidation/fight fields in `Tarinai`, global fight resolution in `World`.
|
||||
- Cause of death: never duplicate regexes in `tarinai.js`; update `HEALTH`.
|
||||
- Public load performance: avoid eager image loads; use `assets.js` lazy/fallback path.
|
||||
|
||||
Validation checklist: `node --check js/*.js`; search for accidental raw Japanese in `.js/.html/.css/.md/.json`; load `index.html` after cache-busting version changes.
|
||||
31
docs/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Architecture
|
||||
|
||||
Build target: static browser page. No bundler. Globals and ordered deferred scripts are intentional.
|
||||
|
||||
## Load order
|
||||
|
||||
`app_manifest.json` owns CSS/JS order. Regenerate `index.html` and `service-worker.js` with `python3 scripts/generate_app_files.py` after version/order changes. `js/version.js` exposes `TARINAI_APP.version`, cache name, and query helper.
|
||||
|
||||
## Runtime objects
|
||||
|
||||
- `World`: constructor in `js/world.js`; methods split into view, family/social, combat/effects, environment, spatial/budget, ants, update, placement/log, bootstrap.
|
||||
- `Tarinai`: constructor in `js/tarinai.js`; methods split into identity/social, disease/nest, item effects, needs/items, social/move/life, render.
|
||||
- `Item`: item state, lifecycle, scaling, and field rendering in `js/items.js`.
|
||||
- `Effect`: transient visuals in `js/sim_core.js`; combat/weather/food effects are spawned through `World` helpers.
|
||||
- `TarinaiEvents`: global event bus for logs, terrain dirtiness, audio failures, phase/performance changes.
|
||||
- `TarinaiPerformance`: FPS quality manager; provides effect caps, ant stride, terrain redraw cadence, UI intervals.
|
||||
|
||||
## Data ownership
|
||||
|
||||
- `js/data.js`: item/tool definitions, config, image metrics, field presets.
|
||||
- `js/text_catalog.js`: Japanese UI copy/tooltips. Keep JS strings escaped where existing style uses escapes.
|
||||
- `js/effect_registry.js`: item-effect display names, exclusivity, random-effect candidates, UI colors.
|
||||
- `js/sound_pack.js`: sound IDs, category, volume multiplier, cooldown, optional sample path.
|
||||
|
||||
## State loops
|
||||
|
||||
`main.js` starts asset preload, world bootstrap, animation loop, deferred image loading, service-worker registration. `World.update(dt)` advances time, weather, items, ants, Tarinai, effects, budgets, terrain dirtiness. `render()` draws cached background/terrain, visible entities, weather overlay, cards, HUD, and debug overlays.
|
||||
|
||||
## Spatial/performance rules
|
||||
|
||||
Use `World.nearby*` helpers rather than full-array scans in hot paths. Terrain cache uses dirty/version flags; do not reintroduce every-frame full terrain hashing. Performance degradation should be centralized in `performance_manager.js`.
|
||||
37
docs/CONTENT_GUIDE.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Content and visual guide
|
||||
|
||||
## Visual source of truth
|
||||
|
||||
Do not invent tool-UI-only item designs. Field visuals in `js/items.js` are primary. Tool icons in `assets/ui/tool_*.webp` are generated previews and must match field rendering. Update `scripts/generate_item_icons.py` when changing item helper silhouettes; regenerate icons before shipping.
|
||||
|
||||
Canonical helper mapping:
|
||||
|
||||
- Weather rain overlay: `drawRain` in `render.js`; diagonal blue line streaks only, never item droplets.
|
||||
- Horizontal item droplet: `drawOvalWaterDropSprite`; used by fallen water items, zunda juice, mercury. Same silhouette; recolor only.
|
||||
- Sleep drug: `drawSleepTabletSprite`.
|
||||
- Laxative: `drawLaxativeTabletSprite`; zunchi-motif tablet.
|
||||
- Mystery drug: `drawMysteryTabletSprite`; question-mark tablet.
|
||||
- Ammo: `drawBulletSprite`; bullet silhouette.
|
||||
- Protein/niteropu: powder-pile rendering in the parameter-item branch.
|
||||
|
||||
## UI copy and labels
|
||||
|
||||
Most visible UI remains Japanese. Keep user-facing naming consistent: `生活` has been renamed to `精神`; selected Tarinai combat summary shows fight win rate, not cumulative defeats.
|
||||
|
||||
## Bubble text inventory
|
||||
|
||||
Active Tarinai/world bubble texts:
|
||||
|
||||
- Symbols: `!`, `?`, `♡`, `...`.
|
||||
- Sleep: `Zzz...`.
|
||||
- Eating/poop: `もぐ`, `ぶりゅっ`, `ぶりゅ…`.
|
||||
- Fear/fight: `はぁぁぁ!`, `はうー!`, `はっ...はっ...`.
|
||||
- Disease/recovery: `うぅ…`, `はう`.
|
||||
- Random small voice: `はう`, `はうぅ`, `はうっ`, `はぅ`.
|
||||
- Item effects: `???`.
|
||||
|
||||
When adding bubbles, keep them short; every bubble may also trigger `audio.bubble`.
|
||||
|
||||
## Audio
|
||||
|
||||
Sound settings are in the top-right sound menu, not inside Event UI. Categories: Tarinai voice, notification, operation. File-backed sound packs belong in `js/sound_pack.js`; missing samples fall back to synthetic audio. Notification and operation sounds are intentionally louder than voice sounds.
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# Internal systems v15.18.0
|
||||
|
||||
Implemented systems except full save/load serialization.
|
||||
|
||||
## Version/cache
|
||||
- `js/version.js` owns the runtime app version.
|
||||
- `index.html` and `service-worker.js` use `15.18.0` cache-busting consistently.
|
||||
- `service-worker.js` is registered with `updateViaCache: "none"`.
|
||||
- `.htaccess` marks `service-worker.js` as no-cache/no-store and static assets as immutable.
|
||||
|
||||
## Event bus
|
||||
- `js/event_bus.js` exposes `window.TarinaiEvents`.
|
||||
- `World.emit/on` forward to the shared event bus.
|
||||
- Current emitted events include `log:entry`, `world:phase`, `tool:place`, `terrain:dirty`, `performance:level`, `ant:nest-built`, and `audio:sample-error`.
|
||||
|
||||
## Performance manager
|
||||
- `js/performance_manager.js` exposes `window.TarinaiPerformance`.
|
||||
- FPS samples are averaged and converted into `level` 0-3.
|
||||
- Ant update stride, effect cap, terrain redraw interval, and UI/stats intervals can now be tuned from one place.
|
||||
|
||||
## Terrain dirty cache
|
||||
- Terrain cache no longer hashes every cached terrain item every frame.
|
||||
- `World.markTerrainDirty(reason)` increments `terrainVersion`.
|
||||
- The cached terrain layer redraws from `terrainVersion`, light bucket, field, and canvas size.
|
||||
|
||||
## Spatial grid
|
||||
- `SpatialGrid` now maintains `itemCells`, `tarinaiCells`, `antCells`, `obstacleCells`, `foodCells`, and `hazardCells`.
|
||||
- `World.nearbyAnts/nearbyObstacles/nearbyFood/nearbyHazards` are available for future behavior/collision work.
|
||||
|
||||
## Debug mode
|
||||
- Add `?debug=1` to the URL to show a lightweight overlay with FPS, performance level, entity counts, terrain/spatial versions, and recent event bus entries.
|
||||
|
||||
## Audio system
|
||||
- Audio has per-ID gain, per-ID minimum gap, a concurrent voice cap, sample load failure reporting, and a test event hook: `TarinaiEvents.emit("audio:test", { id: "sfx_notify" })`.
|
||||
|
||||
|
||||
## File split in 15.18.0
|
||||
|
||||
Large legacy files were split into mixin-style modules. `world.js` and `tarinai.js` now define constructors only; prototype methods are attached by adjacent `world_*` and `tarinai_*` files before `world_bootstrap.js` creates the global `world` instance. This preserves the no-build browser deployment model while reducing edit conflicts and improving maintainability.
|
||||
|
||||
## v15.18.0 internal system pass
|
||||
|
||||
### Static manifest
|
||||
- `app_manifest.json` is the source of truth for CSS and JS loading order.
|
||||
- `scripts/generate_app_files.py` regenerates `index.html` link/script tags and `service-worker.js` cache lists.
|
||||
- This prevents drift between HTML, Service Worker, and versioned cache assets.
|
||||
|
||||
### Effect registry
|
||||
- `js/effect_registry.js` defines item/disease effect metadata in one place: label, color, effect kind, exclusivity group, and display text.
|
||||
- `tarinai_item_effects.js` now reads labels, colors, random-effect candidates, and selected-data display strings from `TarinaiEffectRegistry`.
|
||||
- Future item effects should be registered here first, then only behavior-specific logic should be added to the Tarinai methods.
|
||||
|
||||
### Sound pack
|
||||
- `js/sound_pack.js` defines category gains, ID gains, cooldowns, and optional audio file paths.
|
||||
- `js/audio.js` consumes `window.TARINAI_SOUND_PACK` at startup.
|
||||
- Material replacement should normally edit `sound_pack.js`, not core audio synthesis code.
|
||||
|
||||
### CSS split
|
||||
- `css/styles.css` is now a compatibility shim.
|
||||
- Actual styles are loaded in order: `base.css`, `layout.css`, `panel.css`, `components.css`, `mobile.css`.
|
||||
- Preserve this order unless selector precedence is audited.
|
||||
|
||||
### Mobile input modes
|
||||
- Coarse pointer/mobile displays show a small canvas mode selector.
|
||||
- Modes: automatic, camera, tool, family-tree priority.
|
||||
- Camera mode treats one-finger drag as camera pan; tool mode suppresses accidental panning and favors tap/tool use.
|
||||
|
||||
### Diagnostics
|
||||
- `?debug=1` now shows cache name, Service Worker control state, sound pack version, recent audio IDs, and registered effect count.
|
||||
27
docs/MAINTENANCE.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Maintenance
|
||||
|
||||
## Release process
|
||||
|
||||
1. Edit source files.
|
||||
2. Bump `app_manifest.json` and `js/version.js` to the same version.
|
||||
3. Regenerate static references: `python3 scripts/generate_app_files.py`.
|
||||
4. Regenerate item icons if item visuals changed: `python3 scripts/generate_item_icons.py`.
|
||||
5. Check versions: no stale `v=<old>` in `index.html`, CSS icon URLs, `service-worker.js`.
|
||||
6. Validate syntax: `node --check js/*.js service-worker.js`.
|
||||
7. Zip from project root and run `zip -T`.
|
||||
|
||||
## Cache rules
|
||||
|
||||
`service-worker.js` must remain no-cache/no-store in `.htaccess`; versioned JS/CSS/WebP can be immutable. Register service worker with `updateViaCache: "none"`.
|
||||
|
||||
## Debugging
|
||||
|
||||
Use `?debug=1` for FPS, performance level, counts, cache/SW state, last audio ID, effect registry count, terrain/spatial version, and recent event-bus history.
|
||||
|
||||
## Mobile input
|
||||
|
||||
The mobile mode controls are only for compact no-hover coarse-pointer devices. Desktop browsers, including narrow windows, should not show the mobile controls unless the device is genuinely compact touch-first.
|
||||
|
||||
## Prompt archive
|
||||
|
||||
`docs/prompt.txt` is external user material. Do not rewrite, translate, trim, or delete it.
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
# 効果音ID一覧 v15.12.0
|
||||
|
||||
音素材を差し替える場合は `js/audio.js` の `samplePaths` に、下記IDとファイルパスを追加してください。未指定IDはブラウザ内蔵音で仮再生します。
|
||||
|
||||
## たりないの声
|
||||
- `voice_hau`
|
||||
- `voice_flee`
|
||||
- `voice_poop`
|
||||
- `voice_stress`
|
||||
- `sfx_eat`
|
||||
- `sfx_sleep`
|
||||
- `sfx_wake`
|
||||
- `sfx_birth`
|
||||
- `sfx_death`
|
||||
- `sfx_damage_soft`
|
||||
- `sfx_damage_heavy`
|
||||
- `sfx_fight_hit`
|
||||
- `sfx_fight_finish`
|
||||
- `sfx_disease`
|
||||
- `sfx_heal`
|
||||
- `sfx_ant_drag`
|
||||
- `sfx_ant_die`
|
||||
- `sfx_queen_ant`
|
||||
|
||||
## 通知
|
||||
- `sfx_notify`
|
||||
- `sfx_log_push`
|
||||
- `sfx_phase`
|
||||
|
||||
## 操作
|
||||
- `sfx_ui_click`
|
||||
- `sfx_ui_fold`
|
||||
- `sfx_place`
|
||||
- `sfx_delete`
|
||||
- `sfx_poke`
|
||||
- `sfx_grab`
|
||||
- `sfx_drop`
|
||||
- `sfx_water_hose`
|
||||
- `sfx_water_clean`
|
||||
- `sfx_drink`
|
||||
- `sfx_ball_hit`
|
||||
- `sfx_stone_impact`
|
||||
- `sfx_firecracker_fuse`
|
||||
- `sfx_firecracker_explode`
|
||||
- `sfx_genkotsu_fall`
|
||||
- `sfx_genkotsu_impact`
|
||||
- `sfx_ground_rumble`
|
||||
- `sfx_grass`
|
||||
- `sfx_zunchi`
|
||||
- `sfx_ant_nest`
|
||||
|
||||
## v15.18.0 sound pack
|
||||
|
||||
Sound paths, per-ID volume multipliers, category multipliers, and minimum replay intervals are now centralized in `js/sound_pack.js`.
|
||||
For normal sound replacement, edit `TARINAI_SOUND_PACK.samplePaths` with the same stable sound ID instead of changing `js/audio.js`.
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Code map
|
||||
|
||||
Use this as the first lookup table before opening large files.
|
||||
|
||||
| Need | File / symbol |
|
||||
|---|---|
|
||||
| Script order / DOM shell | `index.html` |
|
||||
| Global config, sprites, object metrics | `js/data.js` / `CONFIG`, `SPRITES`, `IMAGE_METRICS` |
|
||||
| Lazy image loading | `js/assets.js` / `loadInitialImages`, `loadDeferredImages`, `getRenderableImage` |
|
||||
| UI Japanese text catalog | `js/text_catalog.js` / `TEXT_CATALOG` |
|
||||
| Core agent state | `js/tarinai.js` / `class Tarinai` constructor |
|
||||
| Agent AI target choice | `js/tarinai_needs_items.js` / `resolveNeeds`, target helpers |
|
||||
| Disease state transitions | `js/tarinai_disease_nest.js` / `infect*`, `recover*`, `update*Disease*` |
|
||||
| Damage/death cause rules | `js/health.js` / `HEALTH` |
|
||||
| Item creation/lifecycle/draw | `js/items.js` / `class Item` |
|
||||
| Cleaning, rain, placement | `js/world_combat_effects.js`, `js/world_update.js`, `js/world_placement_log.js` |
|
||||
| Fence collision | `js/world_environment.js` / `resolveFenceCollision` |
|
||||
| Logs/archive | `js/world_placement_log.js`, `js/ui_family*.js` |
|
||||
| Main renderer | `js/render.js` / `render` helpers |
|
||||
| Agent drawing | `js/tarinai_render.js` / `draw` |
|
||||
| Input/tools/dialogs | `js/ui.js`, `js/ui_bind.js`, `js/ui_layout_dialogs.js`, `js/ui_tools.js` |
|
||||
| Statistics UI | `js/ui_charts.js` |
|
||||
| Family tree UI | `js/ui_family_data.js`, `js/ui_family_async.js`, `js/ui_family_layout.js`, `js/ui_family_paths.js`, `js/ui_family_render.js` |
|
||||
|
||||
Refactor guidance: extract isolated rule sets into small helper files, then delegate from `Tarinai`/`World`. Keep shared domain terms centralized (`HEALTH`, `TEXT_CATALOG`, `CONFIG`) to reduce repeated prompt context.
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
# たりないコロニー観察所:仕様メモ
|
||||
|
||||
## コンセプト
|
||||
|
||||
「不足を解決するゲーム」ではなく、
|
||||
「不足を抱えた生き物が、それでも勝手に暮らす様子を眺めるゲーム」。
|
||||
|
||||
プレイヤーは神ではなく、観察者兼飼育係程度の立場。
|
||||
すべてを最適化するより、少し足りない状態の揺らぎを観察することを主目的にする。
|
||||
|
||||
## 現在実装しているループ
|
||||
|
||||
1. 個体が空腹・孤独・眠気・不安を内部状態として持つ
|
||||
2. 状態に応じて餌・仲間・寝床を探す
|
||||
3. プレイヤーは環境にアイテムを置いて介入する
|
||||
4. 介入と個体行動により、繁殖・死亡・観察ログが生まれる
|
||||
5. 死亡個体は「たりない跡」を残し、環境に記録として残る
|
||||
|
||||
## 個体状態
|
||||
|
||||
| 状態 | 内容 |
|
||||
|---|---|
|
||||
| 空腹 | 高いほど餌・草を探す。上がりすぎると死亡 |
|
||||
| 孤独 | 高いほど仲間に寄る。近くに仲間がいると下がる |
|
||||
| 眠気残量 | 低いほど寝る。寝床があると回復が速い |
|
||||
| 不安 | 高いほど慌てる。水・石・寝床などで下がる |
|
||||
| たりない度 | 上記の総合的な不安定さ |
|
||||
|
||||
## 個体タイプ
|
||||
|
||||
9枚のスプライトに対応。
|
||||
|
||||
- にこたりない
|
||||
- いかりたりない
|
||||
- うるうるたりない
|
||||
- じとたりない
|
||||
- よだれたりない
|
||||
- ぐしゃたりない
|
||||
- すやたりない
|
||||
- ぽかんたりない
|
||||
- のびたりない
|
||||
|
||||
各タイプは、空腹速度・社交性・睡眠傾向・不安耐性・移動速度に差がある。
|
||||
|
||||
## 介入アイテム
|
||||
|
||||
| アイテム | 役割 |
|
||||
|---|---|
|
||||
| ふつうの餌 | 空腹を直接下げる |
|
||||
| あまい餌 | 空腹と不安を下げるが依存的な挙動を生みやすい |
|
||||
| 水滴 | 不安を下げる。時間で蒸発する |
|
||||
| 草 | 軽い餌・落ち着き場・死後の循環要素 |
|
||||
| ぬくい石 | 周辺温度を上げ、不安を下げる |
|
||||
| ふわふわ | 寝床。睡眠回復を強める |
|
||||
| つつく | 個体に一時的な不安と速度を与える |
|
||||
| 迷い込み | 新規個体を追加する |
|
||||
|
||||
## 生死
|
||||
|
||||
死亡条件は現在、主に以下。
|
||||
|
||||
- 空腹過多
|
||||
- 寿命
|
||||
- 不安過多
|
||||
- 空腹と眠気の複合
|
||||
|
||||
死亡時には「たりない跡」が生成され、一定確率で草も発生する。
|
||||
これは観察ゲームとして「失敗」ではなく、生態系の記録として扱う。
|
||||
|
||||
## 繁殖
|
||||
|
||||
近距離にいる2個体が、以下を満たすと低確率で新個体が発生する。
|
||||
|
||||
- 空腹が高すぎない
|
||||
- 眠気残量が十分ある
|
||||
- 繁殖クールダウンが終わっている
|
||||
- コロニーが過密でない
|
||||
|
||||
子は親のタイプ・足りないものを一部継承し、低確率で変異する。
|
||||
|
||||
## 設計上の注意
|
||||
|
||||
- 完全な最適化ゲームに寄せすぎない
|
||||
- 数値を見せつつ、ログ文で生態観察感を出す
|
||||
- 介入は強いが万能ではない
|
||||
- 死亡や衰退も観察価値として扱う
|
||||
- 「むらさき」は画像作成者名のため、ゲーム内の異常現象名には使用していない
|
||||
|
||||
|
||||
## v3設計メモ
|
||||
|
||||
- 観察時間を伸ばすため、空腹・孤独の上昇をやや抑制し、寿命を長めにした。
|
||||
- 図鑑は「達成」ではなく観察記録として扱う。スプライト、イベント、死因をゆるく解放する。
|
||||
- 系譜は全個体を複雑なツリーで描画せず、出生ログとして読みやすさを優先した。
|
||||
- 音は外部素材を使わず、Web Audioの短い合成音で実装した。ブラウザ仕様上、音 ON を押すまで鳴らない。
|
||||
|
||||
|
||||
## v4 仕様追記
|
||||
|
||||
### 子の追従
|
||||
繁殖で生まれた子は `childGrowTime` の間、親のどちらか近い方を追従する。成長すると通常AIへ移行する。
|
||||
|
||||
### 系譜
|
||||
系譜は `family` レジストリで保持し、親子関係を樹状図で表示する。表示時は同じ個体を重複させず、共有された親子関係は複数の接続線で表す。
|
||||
|
||||
### 食事エフェクト
|
||||
食事パーティクルは表示頻度を抑え、対象アイテムの色を反映する。
|
||||
|
||||
### 喧嘩
|
||||
近距離、低気分、怒り個体、過密などを条件に小さな喧嘩が発生する。喧嘩中は 02_angry を優先表示し、互いにノックバックする。
|
||||
|
||||
### 昼夜
|
||||
夜間・夕方の色を背景だけでなく、観察皿・アイテム・たりない本体にも重ねる。
|
||||
|
||||
|
||||
## v5 仕様追記
|
||||
|
||||
### 草
|
||||
草はコロニー内に自動で発生する。草アイテムは `amount` を草量として持ち、時間でゆっくり伸びる。食べられると `amount` が減り、描画本数も減る。
|
||||
|
||||
### 昼夜の描画修正
|
||||
スプライト本体への色補正は `source-atop` の矩形塗りを廃止し、`ctx.filter` に変更した。これにより透過部分へ矩形状の色が乗る問題を避ける。
|
||||
|
||||
### 系譜
|
||||
図鑑UIは削除し、系譜のみをメイン画面下部に配置する。
|
||||
|
||||
|
||||
## v6 仕様追記
|
||||
|
||||
### 頭突き喧嘩
|
||||
喧嘩中は一定周期で相手へ突進し、近距離でノックバックと喧嘩エフェクトを発生させる。
|
||||
|
||||
### 睡眠
|
||||
睡眠状態では速度をゼロにし、移動処理を行わない。寝床が近くても寝ながら移動しない。
|
||||
|
||||
### カーソル反応
|
||||
カーソルが近い場合、気分の良い個体は笑顔で寄り、不安・怒り・つつき記憶のある個体は逃げる。
|
||||
|
||||
|
||||
## v7 ビジュアル改修
|
||||
|
||||
- 背景をシャーレ風から庭の観察区画風へ変更。
|
||||
- 地面は木枠に囲まれた明るい土床とし、細かな土粒、苔色のムラ、縁の草、野花を加えた。
|
||||
- たりないとアイテムの接地影を追加し、庭の上に実在している感覚を強めた。
|
||||
- 右上HUDと下部ラベルを観察札のような見た目へ調整。
|
||||
- 親子の追従を視認しやすくするため、近距離では薄い点線ガイドを描く。
|
||||
|
||||
|
||||
## v8 ビジュアル / 実装改修
|
||||
|
||||
- 直射日光・月光の方向をもとに、たりないとアイテムへ投影影を付与。
|
||||
- 背景では天体本体は描かず、空色と影方向のみで昼夜を表現する。
|
||||
- 食事の蓄積で「ずんち」を排泄する消化処理を追加。
|
||||
- 09_stretch は通常描画でも横にやや広く扱い、視認性を上げた。
|
||||
- 系譜ツリーの接続線は擬似要素を再設計し、途切れにくい連結表現へ変更。
|
||||
- 旧図鑑用の状態管理(codex)と未使用スタイルを整理。
|
||||
|
||||
|
||||
## v9 修正
|
||||
|
||||
- `ctx.filter = blur(...)` を使った影を廃止し、低負荷な楕円描画へ変更。
|
||||
- 背景装飾のループ数を削減し、毎フレームの描画負荷を下げた。
|
||||
- 死亡時の `splat` は amount を連続的なアルファ値へ反映し、数分かけてフェードアウトする。
|
||||
170
index.html
|
|
@ -4,11 +4,11 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>たりない観察</title>
|
||||
<link rel="stylesheet" href="css/base.css?v=15.19.1" />
|
||||
<link rel="stylesheet" href="css/layout.css?v=15.19.1" />
|
||||
<link rel="stylesheet" href="css/panel.css?v=15.19.1" />
|
||||
<link rel="stylesheet" href="css/components.css?v=15.19.1" />
|
||||
<link rel="stylesheet" href="css/mobile.css?v=15.19.1" />
|
||||
<link rel="stylesheet" href="css/base.css?v=15.20.3" />
|
||||
<link rel="stylesheet" href="css/layout.css?v=15.20.3" />
|
||||
<link rel="stylesheet" href="css/panel.css?v=15.20.3" />
|
||||
<link rel="stylesheet" href="css/components.css?v=15.20.3" />
|
||||
<link rel="stylesheet" href="css/mobile.css?v=15.20.3" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="loadingScreen" class="loading-screen" aria-live="polite">
|
||||
|
|
@ -75,26 +75,32 @@
|
|||
<div class="panel-card-body">
|
||||
<div class="tool-body">
|
||||
<div id="toolPalette" class="tool-palette">
|
||||
<section class="tool-category" data-tool-category="observe">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">観察</button>
|
||||
<section class="tool-category" data-tool-category="operate">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">操作・観察</button>
|
||||
<div class="tool-grid tool-category-body">
|
||||
<button class="tool selected" data-tool="observe">観察</button>
|
||||
<button class="tool" data-tool="delete">削除</button>
|
||||
<button class="tool" data-tool="poke">つつく</button>
|
||||
<button class="tool" data-tool="pinch">つまむ</button>
|
||||
<button class="tool" data-tool="new">追加</button>
|
||||
<button class="tool" data-tool="water_hose">洗浄</button>
|
||||
<button class="tool selected" data-tool="observe">観察</button>
|
||||
<button class="tool" data-tool="delete">削除</button>
|
||||
<button class="tool" data-tool="poke">つつく</button>
|
||||
<button class="tool" data-tool="pinch">つまむ</button>
|
||||
<button class="tool" data-tool="new">追加</button>
|
||||
<button class="tool" data-tool="water_hose">洗浄</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tool-category" data-tool-category="food">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">食べ物・自然</button>
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">食べ物・水・草</button>
|
||||
<div class="tool-grid tool-category-body">
|
||||
<button class="tool" data-tool="zunchi">ずんち</button>
|
||||
<button class="tool" data-tool="sweet">ずんだ餅</button>
|
||||
<button class="tool" data-tool="love_mochi">へこ餅</button>
|
||||
<button class="tool" data-tool="fight_mochi">けんか餅</button>
|
||||
<button class="tool" data-tool="water_bowl">水の皿</button>
|
||||
<button class="tool" data-tool="sleep_drug">ねむり薬</button>
|
||||
<button class="tool" data-tool="sweet">ずんだ餅</button>
|
||||
<button class="tool" data-tool="love_mochi">へこ餅</button>
|
||||
<button class="tool" data-tool="fight_mochi">けんか餅</button>
|
||||
<button class="tool" data-tool="grass">草</button>
|
||||
<button class="tool" data-tool="water_bowl">水の皿</button>
|
||||
<button class="tool" data-tool="zunda_juice">ずんだ汁</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tool-category" data-tool-category="medicine">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">薬・個体効果</button>
|
||||
<div class="tool-grid tool-category-body">
|
||||
<button class="tool" data-tool="sleep_drug">ねむり薬</button>
|
||||
<button class="tool" data-tool="laxative">下剤</button>
|
||||
<button class="tool" data-tool="protein">プロテイン</button>
|
||||
<button class="tool" data-tool="niteropu">ンイテロプ</button>
|
||||
|
|
@ -103,22 +109,26 @@
|
|||
<button class="tool" data-tool="mercury">水銀</button>
|
||||
<button class="tool" data-tool="giant_drug">巨大薬</button>
|
||||
<button class="tool" data-tool="dwarf_drug">矮小薬</button>
|
||||
<button class="tool" data-tool="zunda_juice">ずんだ汁</button>
|
||||
<button class="tool" data-tool="grass">草</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tool-category" data-tool-category="object">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">オブジェクト</button>
|
||||
<section class="tool-category" data-tool-category="habitat">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">生活・生き物</button>
|
||||
<div class="tool-grid tool-category-body">
|
||||
<button class="tool" data-tool="stone">石</button>
|
||||
<button class="tool" data-tool="bed">干草寝床</button>
|
||||
<button class="tool" data-tool="nest_box">巣箱</button>
|
||||
<button class="tool" data-tool="ant_nest">アリの巣</button>
|
||||
<button class="tool" data-tool="ball">ボール</button>
|
||||
<button class="tool" data-tool="genkotsu">げんこつ</button>
|
||||
<button class="tool" data-tool="firecracker">爆竹</button>
|
||||
<button class="tool" data-tool="fence_v">縦の柵</button>
|
||||
<button class="tool" data-tool="fence_h">横の柵</button>
|
||||
<button class="tool" data-tool="zunchi">ずんち</button>
|
||||
<button class="tool" data-tool="bed">干草寝床</button>
|
||||
<button class="tool" data-tool="nest_box">巣箱</button>
|
||||
<button class="tool" data-tool="ant_nest">アリの巣</button>
|
||||
<button class="tool" data-tool="ball">ボール</button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tool-category" data-tool-category="hazard">
|
||||
<button class="tool-category-toggle" type="button" aria-expanded="true">危険・障害物</button>
|
||||
<div class="tool-grid tool-category-body">
|
||||
<button class="tool" data-tool="stone">石</button>
|
||||
<button class="tool" data-tool="genkotsu">げんこつ</button>
|
||||
<button class="tool" data-tool="firecracker">爆竹</button>
|
||||
<button class="tool" data-tool="fence_v">縦の柵</button>
|
||||
<button class="tool" data-tool="fence_h">横の柵</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -227,52 +237,52 @@
|
|||
</div>
|
||||
|
||||
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
|
||||
<script src="js/version.js?v=15.19.1" defer></script>
|
||||
<script src="js/event_bus.js?v=15.19.1" defer></script>
|
||||
<script src="js/performance_manager.js?v=15.19.1" defer></script>
|
||||
<script src="js/effect_registry.js?v=15.19.1" defer></script>
|
||||
<script src="js/sound_pack.js?v=15.19.1" defer></script>
|
||||
<script src="js/data.js?v=15.19.1" defer></script>
|
||||
<script src="js/math.js?v=15.19.1" defer></script>
|
||||
<script src="js/assets.js?v=15.19.1" defer></script>
|
||||
<script src="js/audio.js?v=15.19.1" defer></script>
|
||||
<script src="js/render.js?v=15.19.1" defer></script>
|
||||
<script src="js/sim_core.js?v=15.19.1" defer></script>
|
||||
<script src="js/items.js?v=15.19.1" defer></script>
|
||||
<script src="js/ants.js?v=15.19.1" defer></script>
|
||||
<script src="js/health.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai_identity_social.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai_disease_nest.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai_item_effects.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai_needs_items.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai_social_move_life.js?v=15.19.1" defer></script>
|
||||
<script src="js/tarinai_render.js?v=15.19.1" defer></script>
|
||||
<script src="js/world.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_view.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_family_social.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_combat_effects.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_environment.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_spatial_budget.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_ants_system.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_update.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_placement_log.js?v=15.19.1" defer></script>
|
||||
<script src="js/world_bootstrap.js?v=15.19.1" defer></script>
|
||||
<script src="js/text_catalog.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_log.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_selected.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_layout_dialogs.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_tools.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_bind.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_charts.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_family.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_family_data.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_family_async.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_family_layout.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_family_paths.js?v=15.19.1" defer></script>
|
||||
<script src="js/ui_family_render.js?v=15.19.1" defer></script>
|
||||
<script src="js/main.js?v=15.19.1" defer></script>
|
||||
<script src="js/debug_tools.js?v=15.19.1" defer></script>
|
||||
<script src="js/version.js?v=15.20.3" defer></script>
|
||||
<script src="js/event_bus.js?v=15.20.3" defer></script>
|
||||
<script src="js/performance_manager.js?v=15.20.3" defer></script>
|
||||
<script src="js/effect_registry.js?v=15.20.3" defer></script>
|
||||
<script src="js/sound_pack.js?v=15.20.3" defer></script>
|
||||
<script src="js/data.js?v=15.20.3" defer></script>
|
||||
<script src="js/math.js?v=15.20.3" defer></script>
|
||||
<script src="js/assets.js?v=15.20.3" defer></script>
|
||||
<script src="js/audio.js?v=15.20.3" defer></script>
|
||||
<script src="js/render.js?v=15.20.3" defer></script>
|
||||
<script src="js/sim_core.js?v=15.20.3" defer></script>
|
||||
<script src="js/items.js?v=15.20.3" defer></script>
|
||||
<script src="js/ants.js?v=15.20.3" defer></script>
|
||||
<script src="js/health.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai_identity_social.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai_disease_nest.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai_item_effects.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai_needs_items.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai_social_move_life.js?v=15.20.3" defer></script>
|
||||
<script src="js/tarinai_render.js?v=15.20.3" defer></script>
|
||||
<script src="js/world.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_view.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_family_social.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_combat_effects.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_environment.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_spatial_budget.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_ants_system.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_update.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_placement_log.js?v=15.20.3" defer></script>
|
||||
<script src="js/world_bootstrap.js?v=15.20.3" defer></script>
|
||||
<script src="js/text_catalog.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_log.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_selected.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_layout_dialogs.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_tools.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_bind.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_charts.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_family.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_family_data.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_family_async.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_family_layout.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_family_paths.js?v=15.20.3" defer></script>
|
||||
<script src="js/ui_family_render.js?v=15.20.3" defer></script>
|
||||
<script src="js/main.js?v=15.20.3" defer></script>
|
||||
<script src="js/debug_tools.js?v=15.20.3" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
23
js/ants.js
|
|
@ -265,13 +265,21 @@ class AntActor {
|
|||
}
|
||||
const haulers = Math.max(1, this.activeHaulersForTarget().length);
|
||||
const leadHauler = this.isLeadHauler();
|
||||
const targetWasSleeping = Boolean(target.sleeping || target.state === "sleep");
|
||||
target.antDraggedTimer = Math.max(target.antDraggedTimer || 0, 0.35);
|
||||
target.antDraggedBy = this.id;
|
||||
target.state = "panic";
|
||||
target.sleeping = false;
|
||||
target.target = null;
|
||||
target.fearTimer = Math.max(target.fearTimer || 0, 0.36);
|
||||
target.thought = "\u30a2\u30ea\u306b\u5de3\u3078\u904b\u3070\u308c\u3066\u3044\u308b";
|
||||
if (targetWasSleeping) {
|
||||
target.sleeping = true;
|
||||
target.state = "sleep";
|
||||
target.sleepTimer = Math.max(target.sleepTimer || 0, 0.8);
|
||||
target.thought = "眠ったままアリに運ばれている";
|
||||
} else {
|
||||
target.state = "panic";
|
||||
target.sleeping = false;
|
||||
target.fearTimer = Math.max(target.fearTimer || 0, 0.36);
|
||||
target.thought = "アリに巣へ運ばれている";
|
||||
}
|
||||
const dHome = distXY(target.x, target.y, home.x, home.y) || 1;
|
||||
if (dHome < Math.max(18, home.r * 0.66)) {
|
||||
this.world.completeAntHaul?.(home, target);
|
||||
|
|
@ -298,10 +306,11 @@ class AntActor {
|
|||
this.vy = uy * speed;
|
||||
|
||||
const aggression = Math.max(0, target.currentPersonality?.aggression || 0);
|
||||
const aggressiveAttack = target.shouldApplyPersonalityBehavior?.("aggression", 1) || aggression >= 0.5 || target.state === "ant_attack";
|
||||
const struggle = aggressiveAttack
|
||||
const stillSleeping = Boolean(target.sleeping || targetWasSleeping || target.state === "sleep");
|
||||
const aggressiveAttack = !stillSleeping && (target.shouldApplyPersonalityBehavior?.("aggression", 1) || aggression >= 0.5 || target.state === "ant_attack");
|
||||
const struggle = stillSleeping ? 0 : (aggressiveAttack
|
||||
? (1.25 + aggression * 1.6 + clamp((target.energy || 0) / 100, 0, 1) * 0.35) / Math.max(1, haulers * 0.9)
|
||||
: (0.42 + clamp((target.energy || 0) / 100, 0, 1) * 0.18) / Math.max(1, haulers);
|
||||
: (0.42 + clamp((target.energy || 0) / 100, 0, 1) * 0.18) / Math.max(1, haulers));
|
||||
this.hp -= dt * struggle;
|
||||
|
||||
if ((this.world.time || 0) - (this.carryLogAt || -999) > 8 && Math.random() < dt * 0.04) {
|
||||
|
|
|
|||
16
js/data.js
|
|
@ -51,14 +51,14 @@ const TOOL_DEFINITIONS = Object.freeze({
|
|||
water_bowl: { id: "water_bowl", itemType: "water_bowl", label: "\u6c34\u306e\u76bf", placeable: true, scalable: false, radius: 20, amount: 999, icon: "assets/ui/tool_water_bowl.webp", tooltip: "\u885b\u751f\u7684\u306a\u6c34\u304c\u5165\u3063\u305f\u76bf\u3002\u4e00\u90e8\u306e\u75c5\u6c17\u3092\u6cbb\u305b\u308b\u3002" },
|
||||
sleep_drug: { id: "sleep_drug", itemType: "sleep_drug", label: "\u306d\u3080\u308a\u85ac", placeable: true, scalable: true, radius: 13, amount: 58, icon: "assets/ui/tool_sleep_drug.webp", tooltip: "\u98df\u3079\u308b\u3068\u306d\u3080\u308a\u75c5\u306b\u306a\u308b\u3002\u30c0\u30e1\u30fc\u30b8\u3067\u5b8c\u6cbb\u3059\u308b\u3002" },
|
||||
|
||||
laxative: { id: "laxative", itemType: "laxative", label: "下剤", placeable: true, scalable: true, radius: 13, amount: 58, icon: "assets/ui/tool_laxative.webp", tooltip: "食べると一定時間、3秒ごとにずんちを放出し、食べても空腹度が下がらなくなる。" },
|
||||
protein: { id: "protein", itemType: "protein", label: "プロテイン", placeable: true, scalable: true, radius: 14, amount: 58, icon: "assets/ui/tool_protein.webp", tooltip: "永続。与えるダメージ1.75倍、病気発症確率半減。ンイテロプとは最新効果だけ有効。" },
|
||||
niteropu: { id: "niteropu", itemType: "niteropu", label: "ンイテロプ", placeable: true, scalable: true, radius: 14, amount: 58, icon: "assets/ui/tool_niteropu.webp", tooltip: "永続。与えるダメージ0.5倍、病気発症確率2倍。プロテインとは最新効果だけ有効。" },
|
||||
ammo: { id: "ammo", itemType: "ammo", label: "弾薬", placeable: true, scalable: true, radius: 12, amount: 48, icon: "assets/ui/tool_ammo.webp", tooltip: "効果時間中、歩行速度10倍。移動中にたりないやアリへ衝突すると弾き飛ばす。" },
|
||||
mystery_drug: { id: "mystery_drug", itemType: "mystery_drug", label: "怪しい薬", placeable: true, scalable: true, radius: 13, amount: 52, icon: "assets/ui/tool_mystery_drug.webp", tooltip: "行動を変えるアイテム効果と病気の中からランダムで2つ付与する。" },
|
||||
mercury: { id: "mercury", itemType: "mercury", label: "水銀", placeable: true, scalable: true, radius: 13, amount: 44, icon: "assets/ui/tool_mercury.webp", tooltip: "食べるたびに寿命が1.3倍ずつ乗算される。ずんだ汁で解除できる。" },
|
||||
giant_drug: { id: "giant_drug", itemType: "giant_drug", label: "巨大薬", placeable: true, scalable: true, radius: 22, amount: 52, icon: "assets/ui/tool_giant_drug.webp", tooltip: "永続。サイズ3倍、攻撃力2倍、移動速度1.5倍。毎秒1ダメージ。矮小薬とは最新効果だけ有効。" },
|
||||
dwarf_drug: { id: "dwarf_drug", itemType: "dwarf_drug", label: "矮小薬", placeable: true, scalable: true, radius: 8, amount: 52, icon: "assets/ui/tool_dwarf_drug.webp", tooltip: "永続。サイズ0.25倍、攻撃力0.25倍、移動速度0.5倍。毎秒1ダメージ。巨大薬とは最新効果だけ有効。" },
|
||||
laxative: { id: "laxative", itemType: "laxative", label: "下剤", placeable: true, scalable: true, radius: 13, amount: 58, icon: "assets/ui/tool_laxative.webp", tooltip: "ずんちを大量に排泄させる。何かを食べてもすぐに出て行ってしまう。" },
|
||||
protein: { id: "protein", itemType: "protein", label: "プロテイン", placeable: true, scalable: true, radius: 14, amount: 58, icon: "assets/ui/tool_protein.webp", tooltip: "体をたくましくし、病気に強くなる。" },
|
||||
niteropu: { id: "niteropu", itemType: "niteropu", label: "ンイテロプ", placeable: true, scalable: true, radius: 14, amount: 58, icon: "assets/ui/tool_niteropu.webp", tooltip: "体を頼りなくし、病気に弱くなる。" },
|
||||
ammo: { id: "ammo", itemType: "ammo", label: "弾薬", placeable: true, scalable: true, radius: 12, amount: 48, icon: "assets/ui/tool_ammo.webp", tooltip: "全てを加速させる。" },
|
||||
mystery_drug: { id: "mystery_drug", itemType: "mystery_drug", label: "怪しい薬", placeable: true, scalable: true, radius: 13, amount: 52, icon: "assets/ui/tool_mystery_drug.webp", tooltip: "何かが起こる。" },
|
||||
mercury: { id: "mercury", itemType: "mercury", label: "水銀", placeable: true, scalable: true, radius: 13, amount: 44, icon: "assets/ui/tool_mercury.webp", tooltip: "長生きの秘訣。" },
|
||||
giant_drug: { id: "giant_drug", itemType: "giant_drug", label: "巨大薬", placeable: true, scalable: true, radius: 22, amount: 52, icon: "assets/ui/tool_giant_drug.webp", tooltip: "体を大きく強くするが、負担が大きい。" },
|
||||
dwarf_drug: { id: "dwarf_drug", itemType: "dwarf_drug", label: "矮小薬", placeable: true, scalable: true, radius: 8, amount: 52, icon: "assets/ui/tool_dwarf_drug.webp", tooltip: "体を小さく弱くするが、負担も大きい。" },
|
||||
zunda_juice: { id: "zunda_juice", itemType: "zunda_juice", label: "ずんだ汁", placeable: true, scalable: true, radius: 15, amount: 70, icon: "assets/ui/tool_zunda_juice.webp", tooltip: "食べると全てのアイテム効果を除去する。" },
|
||||
grass: { id: "grass", itemType: "grass", label: "\u8349", placeable: true, scalable: true, radius: 17, amount: 120, icon: "assets/ui/tool_grass.webp", tooltip: "\u98df\u3079\u7269\u3002\u305f\u308a\u306a\u3044\u304c\u5b89\u5fc3\u3059\u308b\u3002" },
|
||||
stone: { id: "stone", itemType: "stone", label: "\u77f3", placeable: true, scalable: true, radius: 20, amount: 999, icon: "assets/ui/tool_stone.webp", tooltip: "\u91cd\u3044\u77f3\u3002\u843d\u3068\u3059\u3068\u5371\u306a\u3044\u3002" },
|
||||
|
|
|
|||
206
js/items.js
|
|
@ -27,6 +27,137 @@ function passiveFoodDecayRate(item) {
|
|||
return PASSIVE_FOOD_DECAY_PER_SECOND * sizeMul * typeMul;
|
||||
}
|
||||
|
||||
function drawOvalWaterDropSprite(ctx, r, palette = {}) {
|
||||
const fill = palette.fill || "rgba(86, 157, 224, 0.82)";
|
||||
const stroke = palette.stroke || "rgba(45, 101, 168, 0.52)";
|
||||
const highlight = palette.highlight || "rgba(255,255,255,0.64)";
|
||||
ctx.save();
|
||||
ctx.fillStyle = fill;
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.lineWidth = Math.max(1.6, r * 0.13);
|
||||
// Horizontal oval item droplet silhouette for fallen water / zunda juice / mercury; rain overlay itself is a separate diagonal-line effect.
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, r * 1.42, r * 0.56, -0.04, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.globalAlpha = 0.72;
|
||||
ctx.fillStyle = highlight;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(-r * 0.46, -r * 0.16, r * 0.34, r * 0.13, -0.18, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawSleepTabletSprite(ctx, r) {
|
||||
ctx.save();
|
||||
ctx.rotate(-0.18);
|
||||
ctx.fillStyle = "#ebe4ff";
|
||||
ctx.strokeStyle = "rgba(74, 60, 144, 0.72)";
|
||||
ctx.lineWidth = Math.max(1.8, r * 0.14);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, r * 0.92, r * 0.66, 0, 0, Math.PI * 2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(112, 91, 188, 0.82)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(r * 0.12, -r * 0.04, r * 0.38, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = "#ebe4ff";
|
||||
ctx.beginPath();
|
||||
ctx.arc(r * 0.28, -r * 0.11, r * 0.38, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = 0.52;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.94)";
|
||||
ctx.lineWidth = Math.max(1.2, r * 0.08);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-r * 0.56, -r * 0.28);
|
||||
ctx.quadraticCurveTo(-r * 0.14, -r * 0.48, r * 0.48, -r * 0.20);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawLaxativeTabletSprite(ctx, r) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#f4d49a";
|
||||
ctx.strokeStyle = "rgba(98, 61, 29, 0.82)";
|
||||
ctx.lineWidth = Math.max(1.8, r * 0.14);
|
||||
roundedRect(ctx, -r * 1.02, -r * 0.54, r * 2.04, r * 1.08, r * 0.34);
|
||||
ctx.fill(); ctx.stroke();
|
||||
// Embedded zunchi mound motif: silhouette-based, not color-only.
|
||||
ctx.fillStyle = "rgba(96, 70, 38, 0.88)";
|
||||
ctx.strokeStyle = "rgba(56, 39, 22, 0.42)";
|
||||
ctx.lineWidth = Math.max(1.0, r * 0.07);
|
||||
const lumps = [
|
||||
[-0.38, 0.11, 0.25, 0.17],
|
||||
[-0.12, -0.03, 0.29, 0.20],
|
||||
[0.20, 0.10, 0.27, 0.18],
|
||||
[0.03, -0.23, 0.20, 0.14],
|
||||
];
|
||||
for (const [x,y,w,h] of lumps) {
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(r*x, r*y, r*w, r*h, 0, 0, Math.PI * 2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
}
|
||||
ctx.globalAlpha = 0.45;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.90)";
|
||||
ctx.lineWidth = Math.max(1.0, r * 0.07);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-r * 0.68, -r * 0.32);
|
||||
ctx.quadraticCurveTo(-r * 0.24, -r * 0.48, r * 0.46, -r * 0.26);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawMysteryTabletSprite(ctx, r) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#f3ecff";
|
||||
ctx.strokeStyle = "rgba(91, 55, 150, 0.84)";
|
||||
ctx.lineWidth = Math.max(1.9, r * 0.15);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, r * 0.94, r * 0.78, 0.10, 0, Math.PI * 2);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(91,55,150,0.92)";
|
||||
ctx.font = `bold ${Math.round(r * 1.35)}px ui-rounded, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("?", 0, -r * 0.04);
|
||||
ctx.globalAlpha = 0.52;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.90)";
|
||||
ctx.lineWidth = Math.max(1.1, r * 0.08);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-r * 0.52, -r * 0.34);
|
||||
ctx.quadraticCurveTo(-r * 0.12, -r * 0.54, r * 0.42, -r * 0.28);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawBulletSprite(ctx, r) {
|
||||
ctx.save();
|
||||
ctx.rotate(0.0);
|
||||
const g = ctx.createLinearGradient(-r, 0, r, 0);
|
||||
g.addColorStop(0, "#b06f23");
|
||||
g.addColorStop(0.48, "#f4c46a");
|
||||
g.addColorStop(1, "#a05a1f");
|
||||
ctx.fillStyle = g;
|
||||
ctx.strokeStyle = "rgba(87, 51, 22, 0.78)";
|
||||
ctx.lineWidth = Math.max(1.7, r * 0.13);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(r * 0.86, 0);
|
||||
ctx.quadraticCurveTo(r * 0.42, -r * 0.52, -r * 0.34, -r * 0.52);
|
||||
ctx.lineTo(-r * 0.88, -r * 0.40);
|
||||
ctx.lineTo(-r * 0.88, r * 0.40);
|
||||
ctx.lineTo(-r * 0.34, r * 0.52);
|
||||
ctx.quadraticCurveTo(r * 0.42, r * 0.52, r * 0.86, 0);
|
||||
ctx.closePath();
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.strokeStyle = "rgba(255,237,176,0.62)";
|
||||
ctx.lineWidth = Math.max(1.0, r * 0.07);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-r * 0.44, -r * 0.34);
|
||||
ctx.lineTo(r * 0.42, -r * 0.20);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
class Item {
|
||||
constructor(type, x, y) {
|
||||
this.id = crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
|
||||
|
|
@ -624,15 +755,15 @@ class Item {
|
|||
}
|
||||
} else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(this.type)) {
|
||||
const meta = {
|
||||
laxative: { fill: "#eef0b8", stroke: "#879046", accent: "#dfe770", kind: "pill" },
|
||||
laxative: { fill: "#f1d18a", stroke: "#774c1f", accent: "#5f3a1b", kind: "laxative" },
|
||||
protein: { fill: "#f4a24f", stroke: "#b45b27", accent: "#ffe2b9", kind: "powder" },
|
||||
niteropu: { fill: "#8a79d0", stroke: "#4c3f91", accent: "#d8d1ff", kind: "powder" },
|
||||
ammo: { fill: "#ffe8b6", stroke: "#b37a26", accent: "#d0942f", kind: "box" },
|
||||
mystery_drug: { fill: "#e9ffe8", stroke: "#4a9b65", accent: "#9de7ad", kind: "pill" },
|
||||
mercury: { fill: "rgba(216,219,220,0.98)", stroke: "rgba(198,202,204,0.98)", accent: "rgba(162,169,174,0.98)", kind: "droplet" },
|
||||
ammo: { fill: "#f4c46a", stroke: "#6f431f", accent: "#ffe8a8", kind: "bullet" },
|
||||
mystery_drug: { fill: "#dff4ff", stroke: "#27587b", accent: "#437c97", kind: "mystery" },
|
||||
mercury: { fill: "rgba(210,216,222,0.94)", stroke: "rgba(112,122,132,0.62)", accent: "rgba(255,255,255,0.72)", kind: "droplet" },
|
||||
giant_drug: { fill: "#ffe3cf", stroke: "#c9632f", accent: "#ffb47b", kind: "pill" },
|
||||
dwarf_drug: { fill: "#e9e9ff", stroke: "#6967ac", accent: "#b8b5f4", kind: "pill" },
|
||||
zunda_juice: { fill: "rgba(134,224,91,0.98)", stroke: "rgba(76,168,57,0.98)", accent: "rgba(202,255,186,0.98)", kind: "droplet" },
|
||||
zunda_juice: { fill: "rgba(125,218,82,0.94)", stroke: "rgba(63,150,50,0.62)", accent: "rgba(238,255,226,0.72)", kind: "droplet" },
|
||||
}[this.type] || { fill: "#fffaf1", stroke: "#8d6f48", accent: "#ddd", kind: "pill" };
|
||||
const localR = this.r * (this.type === "giant_drug" ? 1.08 : (this.type === "dwarf_drug" ? 0.92 : 1));
|
||||
ctx.rotate(Math.sin(this.seed) * 0.10);
|
||||
|
|
@ -662,29 +793,13 @@ class Item {
|
|||
ctx.fill();
|
||||
ctx.restore();
|
||||
} else if (meta.kind === "droplet") {
|
||||
ctx.save();
|
||||
ctx.lineCap = "round";
|
||||
ctx.strokeStyle = meta.stroke;
|
||||
ctx.lineWidth = Math.max(2.2, localR * 0.16);
|
||||
ctx.globalAlpha = 0.92;
|
||||
const lean = -localR * 0.34;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(localR * 0.18, -localR * 0.90);
|
||||
ctx.lineTo(localR * 0.18 + lean, localR * 0.42);
|
||||
ctx.stroke();
|
||||
ctx.globalAlpha = 0.70;
|
||||
ctx.fillStyle = meta.fill;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(localR * 0.18 + lean, localR * 0.54, localR * 0.24, localR * 0.38, -0.18, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.globalAlpha = 0.50;
|
||||
ctx.strokeStyle = meta.accent;
|
||||
ctx.lineWidth = Math.max(1.0, localR * 0.07);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(localR * 0.10, -localR * 0.72);
|
||||
ctx.lineTo(localR * 0.10 + lean * 0.72, localR * 0.18);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
drawOvalWaterDropSprite(ctx, localR, { fill: meta.fill, stroke: meta.stroke, highlight: meta.accent });
|
||||
} else if (meta.kind === "laxative") {
|
||||
drawLaxativeTabletSprite(ctx, localR);
|
||||
} else if (meta.kind === "mystery") {
|
||||
drawMysteryTabletSprite(ctx, localR);
|
||||
} else if (meta.kind === "bullet") {
|
||||
drawBulletSprite(ctx, localR);
|
||||
} else if (meta.kind === "box") {
|
||||
ctx.fillStyle = meta.fill;
|
||||
ctx.strokeStyle = meta.stroke;
|
||||
|
|
@ -721,19 +836,7 @@ class Item {
|
|||
ctx.restore();
|
||||
}
|
||||
} else if (this.type === "sleep_drug") {
|
||||
ctx.fillStyle = "#f5f0ff";
|
||||
ctx.strokeStyle = "rgba(112, 83, 170, 0.55)";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.rotate(-0.45);
|
||||
roundedRect(ctx, -this.r * 0.95, -this.r * 0.45, this.r * 1.90, this.r * 0.90, this.r * 0.45);
|
||||
ctx.fill(); ctx.stroke();
|
||||
ctx.fillStyle = "rgba(152, 116, 218, 0.58)";
|
||||
ctx.fillRect(-1, -this.r * 0.42, 2, this.r * 0.84);
|
||||
ctx.rotate(0.45);
|
||||
ctx.fillStyle = "rgba(255,255,255,0.58)";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(-this.r * 0.36, -this.r * 0.16, this.r * 0.25, this.r * 0.09, -0.20, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
drawSleepTabletSprite(ctx, this.r);
|
||||
} else if (this.type === "genkotsu") {
|
||||
const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu");
|
||||
const impact = clamp(this.impactFlash || 0, 0, 1);
|
||||
|
|
@ -786,22 +889,11 @@ class Item {
|
|||
ctx.fillStyle = "rgba(255,255,255,0.54)";
|
||||
ctx.beginPath(); ctx.arc(-this.r * 0.32, -this.r * 0.18, 2.8, 0, Math.PI * 2); ctx.fill();
|
||||
} else if (this.type === "water") {
|
||||
ctx.save();
|
||||
ctx.lineCap = "round";
|
||||
ctx.strokeStyle = "rgba(198, 202, 204, 0.98)";
|
||||
ctx.lineWidth = Math.max(2.0, this.r * 0.16);
|
||||
const lean = -this.r * 0.34;
|
||||
ctx.globalAlpha = 0.86;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.r * 0.18, -this.r * 0.90);
|
||||
ctx.lineTo(this.r * 0.18 + lean, this.r * 0.42);
|
||||
ctx.stroke();
|
||||
ctx.globalAlpha = 0.58;
|
||||
ctx.fillStyle = "rgba(216, 219, 220, 0.98)";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(this.r * 0.18 + lean, this.r * 0.54, this.r * 0.24, this.r * 0.38, -0.18, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
drawOvalWaterDropSprite(ctx, this.r, {
|
||||
fill: "rgba(86, 157, 224, 0.82)",
|
||||
stroke: "rgba(45, 101, 168, 0.52)",
|
||||
highlight: "rgba(255,255,255,0.68)",
|
||||
});
|
||||
} else if (this.type === "grass") {
|
||||
const growth = this.growth ?? clamp(this.amount / 120, 0, 1);
|
||||
const stage = growth < 0.22 ? "sprout" : growth < 0.48 ? "young" : "mature";
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ loadImages(null, (done, total) => {
|
|||
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
const swUrl = window.TARINAI_APP?.withVersion?.("./service-worker.js") || "./service-worker.js?v=15.19.1";
|
||||
const swUrl = window.TARINAI_APP?.withVersion?.("./service-worker.js") || "./service-worker.js?v=15.20.3";
|
||||
navigator.serviceWorker.register(swUrl, { updateViaCache: "none" }).catch(() => undefined);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
74
js/render.js
|
|
@ -636,56 +636,38 @@ function drawRain(ctx, w, h, t) {
|
|||
if (world.weather !== "light_rain") return;
|
||||
ctx.save();
|
||||
const perf = world.performanceLevel ? world.performanceLevel() : 0;
|
||||
const count = perf >= 2 ? 58 : 118;
|
||||
ctx.lineCap = "round";
|
||||
const count = perf >= 2 ? 64 : 138;
|
||||
const angle = -0.32; // Rain visual effect: diagonal streaks, separate from fallen water item sprites.
|
||||
const slant = Math.tan(angle);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const speed = randSeed(18100 + i, 132, 230);
|
||||
const cycle = h + 320;
|
||||
const len = randSeed(18500 + i, 13, 26);
|
||||
const tilt = randSeed(18600 + i, 5, 12);
|
||||
const fall = randSeed(18400 + i, -220, cycle) + t * speed;
|
||||
const y = ((fall % cycle) + cycle) % cycle - 140;
|
||||
// The droplet's movement vector matches the visible diagonal stroke.
|
||||
const driftPerY = -tilt / Math.max(1, len);
|
||||
const xCycle = w + 220;
|
||||
const rawX = randSeed(18000 + i, -70, w + 120) + fall * driftPerY + Math.sin(t * 0.9 + i * 0.43) * 4;
|
||||
const x = ((rawX % xCycle) + xCycle) % xCycle - 90;
|
||||
const alpha = y < 110 ? clamp((y + 68) / 178, 0, 1) : 1;
|
||||
ctx.globalAlpha = 0.34 + 0.30 * alpha;
|
||||
ctx.strokeStyle = "rgba(196, 200, 202, 0.92)";
|
||||
ctx.lineWidth = randSeed(18700 + i, 1.0, 1.8);
|
||||
const speed = randSeed(18100 + i, 360, 620);
|
||||
const len = randSeed(18500 + i, 14, 28);
|
||||
const cycleY = h + 260;
|
||||
const cycleX = w + 260;
|
||||
const fall = randSeed(18400 + i, -220, cycleY) + t * speed;
|
||||
const y = ((fall % cycleY) + cycleY) % cycleY - 120;
|
||||
const drift = fall * slant * 0.42;
|
||||
const rawX = randSeed(18000 + i, -140, w + 180) + drift;
|
||||
const x = ((rawX % cycleX) + cycleX) % cycleX - 130;
|
||||
const alpha = y < 60 ? clamp((y + 90) / 150, 0, 1) : 1;
|
||||
const dx = Math.sin(angle) * len;
|
||||
const dy = Math.cos(angle) * len;
|
||||
ctx.globalAlpha = (0.18 + 0.34 * randSeed(19000 + i, 0.35, 1.0)) * alpha;
|
||||
ctx.strokeStyle = "rgba(88, 150, 218, 0.86)";
|
||||
ctx.lineWidth = randSeed(18700 + i, 0.8, 1.45);
|
||||
ctx.lineCap = "round";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x - tilt, y + len);
|
||||
ctx.moveTo(x - dx * 0.5, y - dy * 0.5);
|
||||
ctx.lineTo(x + dx * 0.5, y + dy * 0.5);
|
||||
ctx.stroke();
|
||||
if (perf < 3 && i % 2 === 0) {
|
||||
ctx.globalAlpha = 0.18 + 0.18 * alpha;
|
||||
ctx.fillStyle = "rgba(213, 216, 218, 0.92)";
|
||||
if (perf < 3 && i % 4 === 0) {
|
||||
ctx.globalAlpha = 0.13 * alpha;
|
||||
ctx.strokeStyle = "rgba(255,255,255,0.70)";
|
||||
ctx.lineWidth = 0.7;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x - tilt * 0.9, y + len + 2, 1.8, 3.0, -0.28, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
if (perf < 3) {
|
||||
const dripCount = perf >= 2 ? 10 : 18;
|
||||
for (let i = 0; i < dripCount; i++) {
|
||||
const phase = (t * randSeed(18800 + i, 0.55, 1.15) + randSeed(18900 + i, 0, 100)) % 1;
|
||||
const x = randSeed(19000 + i, 12, w - 12);
|
||||
const topY = -22 + phase * (h + 58);
|
||||
const bottomY = topY + randSeed(19100 + i, 14, 30);
|
||||
const lean = randSeed(19200 + i, -6, -2);
|
||||
ctx.globalAlpha = 0.44;
|
||||
ctx.strokeStyle = "rgba(198, 202, 204, 0.98)";
|
||||
ctx.lineWidth = randSeed(19300 + i, 1.2, 2.2);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, topY);
|
||||
ctx.lineTo(x + lean, bottomY);
|
||||
ctx.moveTo(x - dx * 0.5 - 1.5, y - dy * 0.5);
|
||||
ctx.lineTo(x + dx * 0.5 - 1.5, y + dy * 0.5);
|
||||
ctx.stroke();
|
||||
ctx.globalAlpha = 0.26;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(x + lean, bottomY + 2, 2.1, 3.5, -0.18, 0, Math.PI * 2);
|
||||
ctx.fillStyle = "rgba(216, 219, 220, 0.98)";
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
|
|
@ -746,7 +728,6 @@ function render() {
|
|||
|
||||
const perf = world.performanceLevel ? world.performanceLevel() : 0;
|
||||
if (perf < 2) drawLightRays(ctx, screenW, screenH, lighting);
|
||||
if (perf < 3 || world.weather === "light_rain") drawRain(ctx, screenW, screenH, world.time);
|
||||
|
||||
const visibleRect = visibleWorldRect(world, 180);
|
||||
const terrainLayer = ensureTerrainCache(world, lighting);
|
||||
|
|
@ -792,6 +773,7 @@ function render() {
|
|||
|
||||
// Light affecting the whole visible scene, including characters and ground.
|
||||
drawAtmosphericLighting(ctx, screenW, screenH, lighting);
|
||||
if (world.weather === "light_rain") drawRain(ctx, screenW, screenH, world.time);
|
||||
|
||||
const drawAtScreenPosition = (entity, drawFn) => {
|
||||
if (!entity) return;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ class Tarinai { constructor(world, opts = {}) {
|
|||
this.reproductionTimer = rand(4, CONFIG.reproductionCooldown);
|
||||
this.lastLog = 0;
|
||||
this.sleeping = false;
|
||||
this.awakeLockTimer = opts.awakeLockTimer ?? 0;
|
||||
this.wasSleepingLastTick = false;
|
||||
this.blink = rand(0, 10);
|
||||
this.lastSocial = 0;
|
||||
this.goodMode = opts.goodMode ?? stableChoice(this.familyKey, "good-mode", displayNormalSprites()) ?? "smile";
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@
|
|||
this.sleepDiseaseAnchorY = null;
|
||||
this.state = "idle";
|
||||
this.sleeping = false;
|
||||
this.awakeLockTimer = Math.max(this.awakeLockTimer || 0, 10);
|
||||
this.thought = reason || "\u306d\u3080\u308a\u75c5\u304c\u6cbb\u3063\u305f";
|
||||
audio.heal?.();
|
||||
this.surpriseTimer = Math.max(this.surpriseTimer || 0, 0.65);
|
||||
|
|
@ -164,6 +165,7 @@
|
|||
|
||||
freezeSleepDiseaseMotion(dt = 0) {
|
||||
if (!this.sleepDisease) return false;
|
||||
const beingDraggedByAnt = Boolean((this.antDraggedTimer || 0) > 0 && this.antDraggedBy);
|
||||
if (!Number.isFinite(this.sleepDiseaseAnchorX)) this.sleepDiseaseAnchorX = this.x;
|
||||
if (!Number.isFinite(this.sleepDiseaseAnchorY)) this.sleepDiseaseAnchorY = this.y;
|
||||
this.state = "sleep";
|
||||
|
|
@ -171,10 +173,15 @@
|
|||
this.target = null;
|
||||
this.vx = 0;
|
||||
this.vy = 0;
|
||||
this.x = this.sleepDiseaseAnchorX;
|
||||
this.y = this.sleepDiseaseAnchorY;
|
||||
if (beingDraggedByAnt) {
|
||||
this.sleepDiseaseAnchorX = this.x;
|
||||
this.sleepDiseaseAnchorY = this.y;
|
||||
} else {
|
||||
this.x = this.sleepDiseaseAnchorX;
|
||||
this.y = this.sleepDiseaseAnchorY;
|
||||
}
|
||||
this.wanderAngle = this.wanderAngle || 0;
|
||||
this.thought = "\u306d\u3080\u308a\u75c5\u3067\u7720\u308a\u7d9a\u3051\u3066\u3044\u308b";
|
||||
this.thought = beingDraggedByAnt ? "眠ったままアリに運ばれている" : "\u306d\u3080\u308a\u75c5\u3067\u7720\u308a\u7d9a\u3051\u3066\u3044\u308b";
|
||||
if (this.world && this.world.time > (this.nextSleepBubbleAt || 0)) {
|
||||
this.nextSleepBubbleAt = this.world.time + 5.0;
|
||||
this.world.spawnBubble?.(this.x, this.y - this.radius * 1.18, "Zzz...", "rgba(65,70,92,0.72)");
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@
|
|||
this.powerItemMode = mode;
|
||||
this.powerEffectKind = mode;
|
||||
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 3.2);
|
||||
this.bubble(mode === "protein" ? "ムキッ" : "へにゃ", 1.0, ITEM_EFFECT_COLORS[mode]);
|
||||
this.spawnPowerItemEffect(mode, 1.5);
|
||||
return true;
|
||||
},
|
||||
|
|
@ -102,7 +101,6 @@
|
|||
this.refreshEffectiveSize();
|
||||
this.powerEffectKind = mode;
|
||||
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 4.0);
|
||||
this.bubble(mode === "giant_drug" ? "でかっ" : "ちまっ", 1.0, ITEM_EFFECT_COLORS[mode]);
|
||||
this.spawnPowerItemEffect(mode, mode === "giant_drug" ? 1.9 : 1.2);
|
||||
return true;
|
||||
},
|
||||
|
|
@ -118,7 +116,6 @@
|
|||
if (Number.isFinite(this.mercuryLifeSpanBase)) this.lifeSpan = Math.max(this.age + 5, this.mercuryLifeSpanBase * this.mercuryLifeMultiplier);
|
||||
this.powerEffectKind = "mercury";
|
||||
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 2.7);
|
||||
this.bubble(`Hg x${this.mercuryLifeMultiplier.toFixed(2)}`, 1.2, ITEM_EFFECT_COLORS.mercury);
|
||||
this.spawnPowerItemEffect("mercury", 0.95);
|
||||
return true;
|
||||
},
|
||||
|
|
@ -142,7 +139,6 @@
|
|||
this.powerEffectKind = "zunda_juice";
|
||||
this.powerEffectPulse = Math.max(this.powerEffectPulse || 0, 2.2);
|
||||
this.refreshEffectiveSize();
|
||||
this.bubble("すっきり", 1.2, ITEM_EFFECT_COLORS.zunda_juice);
|
||||
this.spawnPowerItemEffect("zunda_juice", 1.4);
|
||||
return true;
|
||||
},
|
||||
|
|
@ -153,7 +149,6 @@
|
|||
let applied = [];
|
||||
if (type === "laxative") {
|
||||
this.setTimedItemEffect("laxative", Math.round(60 * scale));
|
||||
this.bubble("ぐるる", 1.2, ITEM_EFFECT_COLORS.laxative);
|
||||
this.spawnPowerItemEffect("laxative", 0.75);
|
||||
applied.push("laxative");
|
||||
} else if (type === "protein") {
|
||||
|
|
@ -164,7 +159,6 @@
|
|||
applied.push("niteropu");
|
||||
} else if (type === "ammo") {
|
||||
this.setTimedItemEffect("ammo", Math.round(18 * scale));
|
||||
this.bubble("ドン", 0.9, ITEM_EFFECT_COLORS.ammo);
|
||||
this.spawnPowerItemEffect("ammo", 1.1);
|
||||
applied.push("ammo");
|
||||
} else if (type === "mercury") {
|
||||
|
|
|
|||
|
|
@ -232,6 +232,9 @@
|
|||
this.energy = clamp(this.energy, 0, 100);
|
||||
this.stress = clamp(this.stress, 0, 130);
|
||||
|
||||
const wasSleepingAtFrameStart = this.state === "sleep" || this.sleeping;
|
||||
this.awakeLockTimer = Math.max(0, (this.awakeLockTimer || 0) - dt);
|
||||
|
||||
if (this.freezeSleepDiseaseMotion(dt)) {
|
||||
this.checkLife(dt);
|
||||
return;
|
||||
|
|
@ -269,6 +272,8 @@
|
|||
if (this.ammoCollisionKnockback) this.ammoCollisionKnockback(dt);
|
||||
this.updateNestBoxPresence(dt);
|
||||
if (this.world.resolveFenceCollision) this.world.resolveFenceCollision(this);
|
||||
const sleepingAtFrameEnd = this.state === "sleep" || this.sleeping;
|
||||
if (wasSleepingAtFrameStart && !sleepingAtFrameEnd && !this.dead) this.awakeLockTimer = Math.max(this.awakeLockTimer || 0, 10);
|
||||
this.checkLife(dt);
|
||||
},
|
||||
|
||||
|
|
@ -622,8 +627,9 @@
|
|||
}
|
||||
|
||||
const sleepBias = this.personalityProfile().sleep;
|
||||
const wakeLocked = (this.awakeLockTimer || 0) > 0 && !wasSleeping;
|
||||
const keepSleeping = wasSleeping && (personalSleep || this.energy < 66 + 8 * sleepBias);
|
||||
if (keepSleeping || (personalSleep && this.energy < 90 + 8 * sleepBias) || this.energy < 28 + 8 * sleepBias) {
|
||||
if (!wakeLocked && (keepSleeping || (personalSleep && this.energy < 90 + 8 * sleepBias) || this.energy < 28 + 8 * sleepBias)) {
|
||||
let bed = this.isSleepFurniture(previousTarget) ? previousTarget : null;
|
||||
if (!bed && friendNearby && this.isSleepFurniture(friendNearby.target) && (friendNearby.state === "sleep" || friendNearby.state === "seek_bed")) bed = friendNearby.target;
|
||||
if (!bed) bed = this.world.bestBedFor(this, wasSleeping ? 480 : 420);
|
||||
|
|
@ -963,7 +969,7 @@
|
|||
if (this.world.time > this.nextSleepBubbleAt) {
|
||||
this.nextSleepBubbleAt = this.world.time + 4.5;
|
||||
const crowded = occ > 5;
|
||||
this.world.spawnBubble(this.x, this.y - this.radius * 1.18, crowded ? "mm..." : "Zzz...", "rgba(65,70,92,0.72)");
|
||||
this.world.spawnBubble(this.x, this.y - this.radius * 1.18, "Zzz...", "rgba(65,70,92,0.72)");
|
||||
}
|
||||
this.energy += dt * (1.9 + comfort * 1.4);
|
||||
this.stress += dt * (occ > 5 ? 0.55 : -1.3 * comfort);
|
||||
|
|
|
|||
|
|
@ -284,6 +284,19 @@
|
|||
ctx.restore();
|
||||
}
|
||||
}
|
||||
if (sprite === "zunchi_slave") {
|
||||
const zImg = typeof getRenderableImage === "function" ? getRenderableImage("zunchi", "zunchi") : (images && images.get ? images.get("zunchi") : null);
|
||||
if (zImg) {
|
||||
ctx.save();
|
||||
ctx.shadowColor = "rgba(46, 36, 24, 0.28)";
|
||||
ctx.shadowBlur = 5;
|
||||
ctx.shadowOffsetY = Math.max(1, drawH * 0.012);
|
||||
const zw = Math.max(12, drawW * 0.30);
|
||||
const zh = zw * 0.74;
|
||||
ctx.drawImage(zImg, -zw * 0.50, -dh * 0.48 - zh * 0.25, zw, zh);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
drawDawnRimLight(ctx, dw, dh, lightState);
|
||||
if (faceRight) ctx.scale(-1, 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -67,8 +67,12 @@
|
|||
o.vy += ny * rand(34, 64);
|
||||
this.stress = clamp(this.stress + 1.6, 0, 130);
|
||||
o.stress = clamp(o.stress + 2.8, 0, 130);
|
||||
this.damage(o.outgoingDamage ? o.outgoingDamage(1.2) : 1.2, "\u55a7\u5629");
|
||||
o.damage(this.outgoingDamage ? this.outgoingDamage(1.6) : 1.6, "\u55a7\u5629");
|
||||
const damageToThis = o.outgoingDamage ? o.outgoingDamage(1.2) : 1.2;
|
||||
const damageToOther = this.outgoingDamage ? this.outgoingDamage(1.6) : 1.6;
|
||||
this.damage(damageToThis, "\u55a7\u5629");
|
||||
o.damage(damageToOther, "\u55a7\u5629");
|
||||
this.world.maybeDefeatFromFightDamage?.(this, o, damageToThis);
|
||||
this.world.maybeDefeatFromFightDamage?.(o, this, damageToOther);
|
||||
this.hurtTimer = Math.max(this.hurtTimer, 1.4);
|
||||
o.hurtTimer = Math.max(o.hurtTimer, 1.8);
|
||||
this.adjustRelation(o, -0.16, 0.12, "fight");
|
||||
|
|
@ -80,8 +84,7 @@
|
|||
this.world.spawnFallEffect(o.x, o.y + o.radius * 0.45, 0.55);
|
||||
}
|
||||
if (o.energy + o.mood * 0.25 < this.energy + this.mood * 0.25 - 8) {
|
||||
o.defeatedById = this.id;
|
||||
o.fightWinnerId = this.id;
|
||||
this.world.maybeDefeatFromFightDamage?.(o, this, Math.max(damageToOther, 2.4));
|
||||
}
|
||||
this.world.spawnHeadbuttEffect((this.x + o.x) / 2, (this.y + o.y) / 2 - 5);
|
||||
this.nextHeadbutt = this.world.time + rand(0.34, 0.55);
|
||||
|
|
|
|||
|
|
@ -274,7 +274,23 @@ function bindUI() {
|
|||
document.body.classList.toggle("mobile-mode-tool", mode === "tool");
|
||||
document.body.classList.toggle("mobile-mode-family", mode === "family");
|
||||
};
|
||||
const updateTouchMobileUiClass = () => {
|
||||
const ua = String(navigator.userAgent || "");
|
||||
const coarse = Boolean(window.matchMedia && window.matchMedia("(pointer: coarse)").matches);
|
||||
const noHover = Boolean(window.matchMedia && window.matchMedia("(hover: none)").matches);
|
||||
const touchPoints = Number(navigator.maxTouchPoints || 0);
|
||||
const w = window.innerWidth || 9999;
|
||||
const h = window.innerHeight || 9999;
|
||||
const mobileUa = /Android|iPhone|iPod|Mobile|Silk/i.test(ua);
|
||||
const compactPhoneViewport = Math.min(w, h) <= 640 && Math.max(w, h) <= 980;
|
||||
const enabled = Boolean(coarse && noHover && touchPoints > 0 && compactPhoneViewport && mobileUa);
|
||||
document.body.classList.toggle("touch-mobile-ui", enabled);
|
||||
if (ui.mobileModeControls) ui.mobileModeControls.hidden = !enabled;
|
||||
};
|
||||
const mobileInputMode = () => uiCache.mobileInputMode || "auto";
|
||||
updateTouchMobileUiClass();
|
||||
window.addEventListener("resize", updateTouchMobileUiClass, { passive: true });
|
||||
window.matchMedia?.("(pointer: coarse)")?.addEventListener?.("change", updateTouchMobileUiClass);
|
||||
setMobileInputMode(uiCache.mobileInputMode || "auto");
|
||||
ui.mobileModeControls?.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("[data-mobile-mode]");
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ function renderSelected() {
|
|||
<div class="info-row relation-row relation-list-row"><span>\u53cb\u9054</span><strong>${relationListHtml(t, "friend")}</strong></div>
|
||||
<div class="info-row relation-row relation-list-row"><span>\u6575\u5bfe</span><strong>${relationListHtml(t, "enemy")}</strong></div>
|
||||
<div class="info-row"><span>\u55a7\u5629\u8a18\u61b6</span><strong>${escapeHtml(fightRecord)}</strong></div>
|
||||
<div class="info-row"><span>\u7d2f\u8a08\u6557\u5317</span><strong>${t.totalFightLosses || 0}</strong></div>
|
||||
<div class="info-row"><span>けんか勝率</span><strong>${escapeHtml(fightWinRateLabel(t))}</strong></div>
|
||||
`;
|
||||
const familyHtml = `
|
||||
<div class="info-row"><span>\u89aa</span><strong>${escapeHtml(t.parentNames?.length ? t.parentNames.join(" + ") : "\u4e0d\u660e")}</strong></div>
|
||||
|
|
@ -233,6 +233,14 @@ function relationFightRecord(t) {
|
|||
return won || lost ? `\u52dd${won} / \u8ca0${lost}` : "\u306a\u3057";
|
||||
}
|
||||
|
||||
function fightWinRateLabel(t) {
|
||||
const won = Number(t.totalFightWins || 0);
|
||||
const lost = Number(t.totalFightLosses || 0);
|
||||
const total = won + lost;
|
||||
if (!total) return "なし";
|
||||
return `${Math.round((won / total) * 100)}%(${won}勝${lost}敗)`;
|
||||
}
|
||||
|
||||
function setTextIfChanged(el, key, value) {
|
||||
if (!el) return;
|
||||
const next = String(value);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
(function () {
|
||||
const APP_VERSION = "15.19.1";
|
||||
const APP_VERSION = "15.20.3";
|
||||
const APP_BUILD = "systems-split";
|
||||
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
|
||||
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;
|
||||
|
|
|
|||
|
|
@ -405,6 +405,35 @@
|
|||
return true;
|
||||
},
|
||||
|
||||
maybeDefeatFromFightDamage(victim, attacker, damage = 0) {
|
||||
if (!victim || !attacker || victim.dead || attacker.dead || victim.id === attacker.id) return false;
|
||||
if ((victim.fightTimer || 0) <= 0.04 || (attacker.fightTimer || 0) <= 0.04) return false;
|
||||
if (victim.defeatedById || (victim.defeatedTimer || 0) > 0.04) return false;
|
||||
const profile = victim.personalityProfile ? victim.personalityProfile() : { fight: 1 };
|
||||
const aggressionHold = clamp((profile.fight || 1) - 1, -0.8, 1.3);
|
||||
const healthRisk = clamp((42 - (victim.energy || 0)) / 42, 0, 1.25);
|
||||
const damageRisk = clamp((damage || 0) / Math.max(2.5, 9 + (victim.energy || 0) * 0.045), 0, 1.4);
|
||||
const stressRisk = clamp(((victim.stress || 0) - 70) / 70, 0, 0.8);
|
||||
const chance = clamp(0.025 + damageRisk * 0.23 + healthRisk * 0.34 + stressRisk * 0.10 - aggressionHold * 0.12, 0.015, 0.78);
|
||||
if (Math.random() >= chance) return false;
|
||||
victim.defeatedById = attacker.id;
|
||||
victim.fightWinnerId = attacker.id;
|
||||
attacker.fightWinnerId = attacker.id;
|
||||
attacker.defeatedById = null;
|
||||
victim.fightTimer = Math.min(victim.fightTimer || 0, rand(0.20, 0.42));
|
||||
attacker.fightTimer = Math.min(attacker.fightTimer || 0, rand(0.20, 0.42));
|
||||
victim.state = "panic";
|
||||
victim.target = attacker;
|
||||
victim.fearTimer = Math.max(victim.fearTimer || 0, 1.1 * (profile.fear || 1));
|
||||
const dx = victim.x - attacker.x;
|
||||
const dy = victim.y - attacker.y;
|
||||
const d = Math.hypot(dx, dy) || 1;
|
||||
victim.vx += dx / d * rand(38, 76);
|
||||
victim.vy += dy / d * rand(22, 56);
|
||||
this.spawnBubble?.(victim.x, victim.y - victim.radius * 1.25, "はぁぁぁ!", "rgba(84,66,86,0.80)");
|
||||
return true;
|
||||
},
|
||||
|
||||
resolveFightOutcome(winner, loser) {
|
||||
if (!winner || !loser || winner.dead || loser.dead) return;
|
||||
const key = `${winner.id}:${loser.id}:${Math.floor(this.time / 2)}`;
|
||||
|
|
@ -442,9 +471,9 @@
|
|||
if (!it || it.dead || it.type === "water") return;
|
||||
const isStone = it.type === "stone";
|
||||
const isGenkotsu = it.type === "genkotsu";
|
||||
const radius = isGenkotsu ? Math.max(132, (it.r || 88) * 1.74) : (isStone ? 64 : Math.max(34, (it.r || 14) * 1.75));
|
||||
const radius = isGenkotsu ? Math.max(164, (it.r || 88) * 2.05) : (isStone ? 64 : Math.max(34, (it.r || 14) * 1.75));
|
||||
const damage = isGenkotsu ? rand(38, 58) : (isStone ? rand(20, 34) : rand(3, 7));
|
||||
const push = isGenkotsu ? rand(360, 520) : (isStone ? rand(145, 210) : rand(54, 92));
|
||||
const push = isGenkotsu ? rand(760, 1040) : (isStone ? rand(145, 210) : rand(54, 92));
|
||||
const reason = isGenkotsu ? "\u3052\u3093\u3053\u3064" : (isStone ? "\u77f3\u304c\u843d\u3061\u305f" : "\u843d\u3061\u3066\u304d\u305f\u9053\u5177\u306b\u5f53\u305f\u3063\u305f");
|
||||
if (isGenkotsu) { it.impactFlash = 1; it.lingerTimer = Math.max(it.lingerDuration || 2, 2); this.triggerGroundShake?.(2.0, 10); }
|
||||
let hit = 0;
|
||||
|
|
@ -461,8 +490,15 @@
|
|||
ny = Math.sin(a);
|
||||
}
|
||||
t.vx += nx * push * (0.35 + p);
|
||||
t.vy += ny * push * (0.35 + p) - (isGenkotsu ? 92 : (isStone ? 42 : 18)) * p;
|
||||
t.damage(damage * (0.35 + p * 0.65), reason);
|
||||
t.vy += ny * push * (0.35 + p) - (isGenkotsu ? 190 : (isStone ? 42 : 18)) * p;
|
||||
const dealtDamage = damage * (0.35 + p * 0.65);
|
||||
t.damage(dealtDamage, reason);
|
||||
if (isGenkotsu) {
|
||||
t.fearTimer = Math.max(t.fearTimer || 0, 4.0 + p * 2.2);
|
||||
t.stress = clamp((t.stress || 0) + 10 + p * 18, 0, 130);
|
||||
t.adjustPersonality?.("openness", -(0.020 + p * 0.030), "after being struck by genkotsu.");
|
||||
t.thought = "げんこつがこわい";
|
||||
}
|
||||
t.hurtTimer = Math.max(t.hurtTimer, isGenkotsu ? 3.4 : (isStone ? 2.7 : 1.25));
|
||||
t.fallTimer = Math.max(t.fallTimer, isGenkotsu ? 0.86 : (isStone ? 0.62 : 0.26));
|
||||
t.fallMax = Math.max(t.fallMax || t.fallTimer, t.fallTimer);
|
||||
|
|
@ -470,9 +506,10 @@
|
|||
hit += 1;
|
||||
}
|
||||
const antHit = this.damageAntsInRadius(it.x, it.y, radius + 54, p => damage * (0.35 + p * 0.65), reason, {
|
||||
push: p => push * (0.35 + p),
|
||||
push: p => push * (0.62 + p * 1.25),
|
||||
});
|
||||
if (isGenkotsu) {
|
||||
this.blastZunchiFrom?.(it.x, it.y, radius + 92, 2.75);
|
||||
for (let r = 0; r < 4; r++) this.effects.push(new Effect("ring", it.x, it.y, { size: 32 + r * 34, life: 0.34 + r * 0.07, color: r % 2 ? "rgba(255,203,54,0.72)" : "rgba(74,57,42,0.42)" }));
|
||||
for (let i = 0; i < 12; i++) this.effects.push(new Effect("fight", it.x + rand(-38, 38), it.y + rand(-22, 28), { size: rand(12, 22), life: rand(0.25, 0.48), color: "rgba(255,205,46,0.82)" }));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -713,21 +713,14 @@
|
|||
b.fightTargetIds = b.fightTargetIds.slice(-4);
|
||||
a.fightTargetId = a.fightTargetIds[0];
|
||||
b.fightTargetId = b.fightTargetIds[0];
|
||||
const aAggressiveIntent = a.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
|
||||
const bAggressiveIntent = b.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
|
||||
const aScore = a.energy * 0.52 + a.mood * 0.30 + (a.type === "angry" ? 10 : 0) + aAggressiveIntent * 5 + ((a.fightMochiTimer || 0) > 0.04 ? 12 : 0) + (a.isZunchiSlave ? -14 : 0) + rand(-7, 7);
|
||||
const bScore = b.energy * 0.52 + b.mood * 0.30 + (b.type === "angry" ? 10 : 0) + bAggressiveIntent * 5 + ((b.fightMochiTimer || 0) > 0.04 ? 12 : 0) + (b.isZunchiSlave ? -14 : 0) + rand(-7, 7);
|
||||
const loser = aScore <= bScore ? a : b;
|
||||
const winner = loser === a ? b : a;
|
||||
loser.defeatedById = winner.id;
|
||||
loser.fightWinnerId = winner.id;
|
||||
winner.fightWinnerId = winner.id;
|
||||
winner.defeatedById = null;
|
||||
winner.defeatedTimer = 0;
|
||||
a.addStress ? a.addStress(rand(8, 18), { threshold: 8 }) : (a.stress = clamp(a.stress + rand(8, 18), 0, 130));
|
||||
b.addStress ? b.addStress(rand(8, 18), { threshold: 8 }) : (b.stress = clamp(b.stress + rand(8, 18), 0, 130));
|
||||
a.damage(b.outgoingDamage ? b.outgoingDamage(rand(2, 7)) : rand(2, 7), "\u55a7\u5629");
|
||||
b.damage(a.outgoingDamage ? a.outgoingDamage(rand(2, 7)) : rand(2, 7), "\u55a7\u5629");
|
||||
const damageToA = b.outgoingDamage ? b.outgoingDamage(rand(2, 7)) : rand(2, 7);
|
||||
const damageToB = a.outgoingDamage ? a.outgoingDamage(rand(2, 7)) : rand(2, 7);
|
||||
a.damage(damageToA, "\u55a7\u5629");
|
||||
b.damage(damageToB, "\u55a7\u5629");
|
||||
this.maybeDefeatFromFightDamage?.(a, b, damageToA);
|
||||
this.maybeDefeatFromFightDamage?.(b, a, damageToB);
|
||||
const dx = b.x - a.x, dy = b.y - a.y;
|
||||
const d = Math.hypot(dx, dy) || 1;
|
||||
a.vx -= dx / d * rand(32, 62); a.vy -= dy / d * rand(32, 62);
|
||||
|
|
|
|||
|
|
@ -70,10 +70,10 @@
|
|||
},
|
||||
|
||||
phaseName() {
|
||||
const p = this.dayProgress();
|
||||
if (p < 0.20) return "\u671d";
|
||||
if (p < 0.48) return "\u663c";
|
||||
if (p < 0.68) return "\u5915\u65b9";
|
||||
const hour = this.dayProgress() * 24;
|
||||
if (hour >= 5 && hour < 10) return "\u671d";
|
||||
if (hour >= 10 && hour < 17) return "\u663c";
|
||||
if (hour >= 17 && hour < 20) return "\u5915\u65b9";
|
||||
return "\u591c";
|
||||
},
|
||||
|
||||
|
|
@ -285,12 +285,14 @@
|
|||
},
|
||||
|
||||
toolSizeFor(type = "") {
|
||||
if (!["firecracker", "fence_v", "fence_h", "stone", "grass", "food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "zunchi"].includes(type)) return "medium";
|
||||
const def = typeof itemDefinition === "function" ? itemDefinition(type) : null;
|
||||
if (!def?.scalable) return "medium";
|
||||
return (this.toolSizes && this.toolSizes[type]) || this.toolSize || "medium";
|
||||
},
|
||||
|
||||
toolSizeScale(type = "") {
|
||||
if (!["firecracker", "fence_v", "fence_h", "stone", "grass", "food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "zunchi"].includes(type)) return 1;
|
||||
const def = typeof itemDefinition === "function" ? itemDefinition(type) : null;
|
||||
if (!def?.scalable) return 1;
|
||||
return { small: 0.68, medium: 1.0, large: 1.48 }[this.toolSizeFor(type)] || 1;
|
||||
},
|
||||
|
||||
|
|
|
|||
81
scripts/generate_item_icons.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate UI icons for item designs that must match js/items.js.
|
||||
|
||||
The game is buildless, but these icons are generated snapshots of the field
|
||||
appearance. Do not invent separate tool-UI-only designs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import math
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / "assets" / "ui"
|
||||
|
||||
|
||||
def save_icon(name: str, draw_fn) -> None:
|
||||
im = Image.new("RGBA", (96, 96), (0, 0, 0, 0))
|
||||
d = ImageDraw.Draw(im, "RGBA")
|
||||
draw_fn(d)
|
||||
im.save(OUT / name, "WEBP", lossless=True, quality=95)
|
||||
|
||||
|
||||
def draw_drop(fill, stroke, highlight):
|
||||
def fn(d: ImageDraw.ImageDraw) -> None:
|
||||
d.ellipse([18, 60, 78, 74], fill=(60, 45, 35, 32))
|
||||
# Same horizontal oval as fallen water/zunda/mercury item sprites; rain overlay is separate diagonal lines.
|
||||
d.ellipse([12, 33, 84, 63], fill=fill, outline=stroke, width=4)
|
||||
d.ellipse([24, 38, 45, 48], fill=highlight)
|
||||
return fn
|
||||
|
||||
|
||||
def draw_laxative(d: ImageDraw.ImageDraw) -> None:
|
||||
d.rounded_rectangle([14, 28, 82, 68], radius=15, fill=(244, 212, 154, 255), outline=(98, 61, 29, 235), width=4)
|
||||
# Zunchi mound motif inside the tablet.
|
||||
for box in ([26, 47, 44, 60], [38, 36, 60, 53], [54, 47, 74, 61], [46, 29, 61, 41]):
|
||||
d.ellipse(box, fill=(96, 70, 38, 230), outline=(56, 39, 22, 130), width=2)
|
||||
d.arc([22, 27, 72, 50], 205, 330, fill=(255, 255, 255, 135), width=3)
|
||||
|
||||
def star_points(cx: float, cy: float, r1: float, r2: float, n: int = 5, rot: float = -math.pi / 2):
|
||||
return [(cx + math.cos(rot + i * math.pi / n) * (r1 if i % 2 == 0 else r2), cy + math.sin(rot + i * math.pi / n) * (r1 if i % 2 == 0 else r2)) for i in range(n * 2)]
|
||||
|
||||
|
||||
def draw_mystery(d: ImageDraw.ImageDraw) -> None:
|
||||
d.ellipse([18, 23, 78, 73], fill=(243, 236, 255, 255), outline=(91, 55, 150, 240), width=5)
|
||||
try:
|
||||
from PIL import ImageFont
|
||||
font = ImageFont.truetype("DejaVuSans-Bold.ttf", 44)
|
||||
except Exception:
|
||||
font = None
|
||||
d.text((48, 48), "?", anchor="mm", fill=(91, 55, 150, 245), font=font)
|
||||
d.arc([25, 24, 74, 54], 205, 330, fill=(255, 255, 255, 135), width=3)
|
||||
|
||||
def draw_bullet(d: ImageDraw.ImageDraw) -> None:
|
||||
d.ellipse([20, 62, 76, 77], fill=(60, 45, 35, 38))
|
||||
pts = [(76, 48), (60, 28), (27, 28), (16, 35), (16, 61), (27, 68), (60, 68)]
|
||||
d.polygon(pts, fill=(244, 196, 106, 255), outline=(87, 51, 22, 240))
|
||||
d.line(pts + [pts[0]], fill=(87, 51, 22, 240), width=4)
|
||||
d.rectangle([16, 35, 29, 61], fill=(176, 111, 35, 255), outline=(87, 51, 22, 220), width=3)
|
||||
d.line([(34, 36), (64, 42)], fill=(255, 237, 176, 170), width=4)
|
||||
d.line([(34, 60), (58, 56)], fill=(142, 82, 30, 110), width=3)
|
||||
|
||||
|
||||
def draw_sleep(d: ImageDraw.ImageDraw) -> None:
|
||||
d.ellipse([17, 28, 79, 68], fill=(235, 228, 255, 255), outline=(74, 60, 144, 230), width=4)
|
||||
d.ellipse([41, 31, 65, 56], fill=(112, 91, 188, 220))
|
||||
d.ellipse([52, 27, 74, 52], fill=(235, 228, 255, 255))
|
||||
d.arc([24, 27, 74, 57], 200, 330, fill=(255, 255, 255, 190), width=3)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
save_icon("tool_laxative.webp", draw_laxative)
|
||||
save_icon("tool_mystery_drug.webp", draw_mystery)
|
||||
save_icon("tool_ammo.webp", draw_bullet)
|
||||
save_icon("tool_sleep_drug.webp", draw_sleep)
|
||||
save_icon("tool_water.webp", draw_drop((86, 157, 224, 220), (45, 101, 168, 150), (255, 255, 255, 150)))
|
||||
save_icon("tool_zunda_juice.webp", draw_drop((125, 218, 82, 235), (63, 150, 50, 160), (238, 255, 226, 160)))
|
||||
save_icon("tool_mercury.webp", draw_drop((210, 216, 222, 240), (112, 122, 132, 165), (255, 255, 255, 170)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
const APP_VERSION = "15.19.1";
|
||||
const APP_VERSION = "15.20.3";
|
||||
const CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
|
||||
const v = `v=${APP_VERSION}`;
|
||||
// Generated from app_manifest.json. Run scripts/generate_app_files.py after changing static files.
|
||||
|
|
|
|||