desire
This commit is contained in:
parent
54aff02658
commit
8bd4405fe8
33 changed files with 2478 additions and 610 deletions
|
|
@ -1,23 +1,17 @@
|
||||||
# v15.24.2 needs compliance fix
|
# Patch Notes 15.24.12
|
||||||
|
|
||||||
This patch focuses on making the implementation closer to the need-system prompt.
|
- Added `activeBehavior` as the authoritative runtime behavior layer above legacy state timers.
|
||||||
|
- Added `forcedBehaviorQueue` and `forceBehavior()` hook for external forced actions such as love mochi, fight mochi, and drugs.
|
||||||
|
- Re-routed love mochi, fight mochi, and sleep drug effects through forced need actions.
|
||||||
|
- Converted eating, panic, fighting, intimidation, and birth ritual display/scheduling to behavior-backed actions.
|
||||||
|
- Split social need pressure internally into bond/family/mate/conflict/fear parts and used those parts in social action selection.
|
||||||
|
- Weighted fulfill action selection by its internal reason parts.
|
||||||
|
- Reduced direct old-style birth starts: proximity now raises mate pressure; ritual starts when mate behavior is active or forced.
|
||||||
|
- UI action text now prefers `activeBehavior` over stale thoughts or intent strings.
|
||||||
|
|
||||||
## Fixed
|
## 15.24.14
|
||||||
|
- 外部強制行動を強化。薬・餅から `forceBehavior()` 経由で優先割込み、対象再探索、失敗時再試行を行うようにした。
|
||||||
- Kept the six-need system as the behavior driver.
|
- へこ餅、けんか餅、ねむり薬の強制行動優先度を更新。
|
||||||
- Kept needs clamped/quantized and displayed as 0-10 values.
|
- 喧嘩対象が Item 等になった場合の `relationTo is not a function` エラーを防止。
|
||||||
- Kept tied top-need random selection and tie reason text.
|
- 睡眠欲求を概日圧 `circadianSleepPressure` 中心に変更。夜は大きく上昇、昼下がりは微増、それ以外は減衰。
|
||||||
- Kept StructureRegistry and both buildable structures: grass_bed and plushie.
|
- 初期生成時、夜なら睡眠欲求が高めになるよう初期エネルギーと概日圧を調整。
|
||||||
- Removed remaining mojibake strings in the main need/action implementation file.
|
|
||||||
- Changed safety fleeing so normal safety behavior no longer directly enters panic.
|
|
||||||
- Reworked stress so direct stress mutation is removed from the main behavior files.
|
|
||||||
- Stress is now recalculated from needs or projected needs via calculateStressFromNeeds.
|
|
||||||
- Added applyNeedShock/applyNeedRelief helpers and converted shock/relief events into need changes.
|
|
||||||
- Reworked enterPanic as a legacy adapter that converts old calls into need shock first.
|
|
||||||
- Panic/intimidate are now short reaction states triggered through need shock logic.
|
|
||||||
- Updated cache/version strings to 15.24.2.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Old save compatibility is intentionally not provided.
|
|
||||||
- Some legacy fields such as loneliness, affection, mood, and fearTimer still exist as compatibility/input/display fields, but the main action selection now uses the six needs.
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "15.24.2",
|
"version": "15.24.14",
|
||||||
"css": [
|
"css": [
|
||||||
"css/base.css",
|
"css/base.css",
|
||||||
"css/layout.css",
|
"css/layout.css",
|
||||||
|
|
|
||||||
BIN
assets/ui/tool_duplicator.webp
Normal file
BIN
assets/ui/tool_duplicator.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/ui/tool_oshibyo.webp
Normal file
BIN
assets/ui/tool_oshibyo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/ui/tool_signboard.webp
Normal file
BIN
assets/ui/tool_signboard.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 968 B |
104
css/base.css
104
css/base.css
|
|
@ -384,6 +384,20 @@ h1 {
|
||||||
opacity: 0.10;
|
opacity: 0.10;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool.tool-has-canvas-icon { padding-left: 42px; --tool-icon: none; }
|
||||||
|
.tool.tool-has-canvas-icon::before,
|
||||||
|
.tool.tool-has-canvas-icon::after { background: none !important; }
|
||||||
|
.tool-sprite-preview {
|
||||||
|
position: absolute;
|
||||||
|
left: 3px;
|
||||||
|
top: 50%;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12));
|
||||||
|
}
|
||||||
.tool > span:not(.tool-size-badge) { display: none; }
|
.tool > span:not(.tool-size-badge) { display: none; }
|
||||||
.tool[data-tool="observe"] { --tool-icon: url("../assets/ui/tool_observe.webp"); }
|
.tool[data-tool="observe"] { --tool-icon: url("../assets/ui/tool_observe.webp"); }
|
||||||
.tool[data-tool="zunchi"] { --tool-icon: url("../assets/ui/tool_zunchi.webp"); }
|
.tool[data-tool="zunchi"] { --tool-icon: url("../assets/ui/tool_zunchi.webp"); }
|
||||||
|
|
@ -399,6 +413,11 @@ h1 {
|
||||||
.tool[data-tool="fight_mochi"] { --tool-icon: url("../assets/ui/tool_fight_mochi.webp"); }
|
.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"); }
|
.tool[data-tool="water_bowl"] { --tool-icon: url("../assets/ui/tool_water_bowl.webp"); }
|
||||||
|
|
||||||
|
.tool[data-tool="signboard"] { --tool-icon: url("../assets/ui/tool_signboard.webp"); }
|
||||||
|
.tool[data-tool="duplicator"] { --tool-icon: url("../assets/ui/tool_duplicator.webp"); }
|
||||||
|
.tool[data-tool="pushpin"] { --tool-icon: url("../assets/ui/tool_pushpin.webp"); }
|
||||||
|
.tool[data-tool="oshibyo"] { --tool-icon: url("../assets/ui/tool_oshibyo.webp"); }
|
||||||
|
|
||||||
/* Generated parameter item tool icons. */
|
/* Generated parameter item tool icons. */
|
||||||
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.20.16"); }
|
.tool[data-tool="laxative"] { --tool-icon: url("../assets/ui/tool_laxative.webp?v=15.20.16"); }
|
||||||
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.20.16"); }
|
.tool[data-tool="protein"] { --tool-icon: url("../assets/ui/tool_protein.webp?v=15.20.16"); }
|
||||||
|
|
@ -409,12 +428,81 @@ h1 {
|
||||||
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.20.16"); }
|
.tool[data-tool="giant_drug"] { --tool-icon: url("../assets/ui/tool_giant_drug.webp?v=15.20.16"); }
|
||||||
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.20.16"); }
|
.tool[data-tool="dwarf_drug"] { --tool-icon: url("../assets/ui/tool_dwarf_drug.webp?v=15.20.16"); }
|
||||||
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.20.16"); }
|
.tool[data-tool="zunda_juice"] { --tool-icon: url("../assets/ui/tool_zunda_juice.webp?v=15.20.16"); }
|
||||||
.tool[data-tool="freeze"] { --tool-icon: none; }
|
|
||||||
.tool[data-tool="freeze"]::before {
|
|
||||||
content: "🧊";
|
.signboard-editor {
|
||||||
background: none;
|
position: fixed;
|
||||||
font-size: 24px;
|
inset: 0;
|
||||||
line-height: 1;
|
z-index: 42;
|
||||||
text-align: center;
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 18px;
|
||||||
|
background: rgba(34, 29, 22, 0.34);
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
}
|
}
|
||||||
.tool[data-tool="freeze"]::after { content: "🧊"; background: none; font-size: 52px; right: -10px; left: auto; opacity: 0.08; display:flex; align-items:center; justify-content:flex-end; }
|
.signboard-editor.hidden { display: none; }
|
||||||
|
.signboard-editor-panel {
|
||||||
|
width: min(430px, calc(100vw - 28px));
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 14px;
|
||||||
|
background: linear-gradient(180deg, rgba(255,253,247,0.98), rgba(245,237,221,0.96));
|
||||||
|
border: 1px solid rgba(94, 70, 42, 0.20);
|
||||||
|
box-shadow: 0 22px 60px rgba(36, 28, 20, 0.28), inset 0 1px 0 rgba(255,255,255,0.72);
|
||||||
|
}
|
||||||
|
.signboard-editor-titlebar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0 0 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid rgba(94, 70, 42, 0.13);
|
||||||
|
}
|
||||||
|
.signboard-editor-titlebar h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: .03em;
|
||||||
|
}
|
||||||
|
.signboard-editor-close {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(84,68,48,0.16);
|
||||||
|
background: rgba(255,255,255,0.72);
|
||||||
|
color: var(--ink);
|
||||||
|
font-size: 19px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.signboard-editor-close:hover { background: rgba(255,248,232,0.96); }
|
||||||
|
#signboardEditorText {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 88px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(84,68,48,0.18);
|
||||||
|
background: rgba(255,255,255,0.84);
|
||||||
|
padding: 10px 11px;
|
||||||
|
color: var(--ink);
|
||||||
|
font: 14px/1.55 ui-rounded, "Hiragino Maru Gothic ProN", "Yu Gothic", sans-serif;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#signboardEditorText:focus {
|
||||||
|
border-color: rgba(112,178,66,0.54);
|
||||||
|
box-shadow: 0 0 0 3px rgba(112,178,66,0.14);
|
||||||
|
}
|
||||||
|
.signboard-editor-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 6px 2px 0;
|
||||||
|
}
|
||||||
|
.signboard-editor-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -451,13 +451,34 @@
|
||||||
.selected-info .info-row[data-metric="loneliness"] { background: rgba(116, 150, 214, 0.10); }
|
.selected-info .info-row[data-metric="loneliness"] { background: rgba(116, 150, 214, 0.10); }
|
||||||
.selected-info .info-row[data-metric="energy"] { background: rgba(104, 184, 96, 0.10); }
|
.selected-info .info-row[data-metric="energy"] { background: rgba(104, 184, 96, 0.10); }
|
||||||
.selected-info .info-row[data-metric="stress"] { background: rgba(214, 104, 88, 0.10); }
|
.selected-info .info-row[data-metric="stress"] { background: rgba(214, 104, 88, 0.10); }
|
||||||
.selected-info .need-row[data-chosen-need="true"] {
|
.selected-info .need-grid {
|
||||||
border-color: rgba(65, 112, 74, 0.52);
|
display: grid;
|
||||||
background: rgba(106, 154, 84, 0.16);
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
margin: 7px 0 8px;
|
||||||
}
|
}
|
||||||
.selected-info .need-row[data-chosen-need="true"] strong {
|
.selected-info .need-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 6px 4px;
|
||||||
|
border: 1px solid rgba(84,68,48,0.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255,255,255,0.42);
|
||||||
|
}
|
||||||
|
.selected-info .need-cell span { font-size: 11px; color: var(--muted); }
|
||||||
|
.selected-info .need-cell strong { font-size: 16px; color: #3f3225; }
|
||||||
|
.selected-info .need-cell[data-chosen-need="true"] {
|
||||||
|
border-color: rgba(65, 112, 74, 0.58);
|
||||||
|
background: rgba(106, 154, 84, 0.20);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(65, 112, 74, 0.18);
|
||||||
|
}
|
||||||
|
.selected-info .need-cell[data-chosen-need="true"] strong {
|
||||||
color: #315f32;
|
color: #315f32;
|
||||||
}
|
}
|
||||||
|
.selected-info .action-reason-row strong { text-align: right; }
|
||||||
.selected-info .info-row[data-metric="zunchi"] { background: rgba(112, 142, 70, 0.10); }
|
.selected-info .info-row[data-metric="zunchi"] { background: rgba(112, 142, 70, 0.10); }
|
||||||
.selected-info .info-row[data-metric="mood"] { background: rgba(222, 196, 86, 0.10); }
|
.selected-info .info-row[data-metric="mood"] { background: rgba(222, 196, 86, 0.10); }
|
||||||
.selected-info .info-row[data-metric="lack"] { background: rgba(142, 112, 184, 0.10); }
|
.selected-info .info-row[data-metric="lack"] { background: rgba(142, 112, 184, 0.10); }
|
||||||
|
|
@ -566,19 +587,29 @@
|
||||||
}
|
}
|
||||||
.save-slot-list {
|
.save-slot-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
margin: 14px 0;
|
margin: 14px 0;
|
||||||
}
|
}
|
||||||
.save-slot-row {
|
.save-slot-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(86px, 0.7fr) minmax(180px, 2fr) auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
padding: 9px 10px;
|
min-height: 132px;
|
||||||
|
padding: 10px;
|
||||||
border: 1px solid rgba(115, 91, 62, 0.20);
|
border: 1px solid rgba(115, 91, 62, 0.20);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: rgba(255, 255, 255, 0.56);
|
background: rgba(255, 255, 255, 0.56);
|
||||||
}
|
}
|
||||||
|
.save-warning {
|
||||||
|
color: #8a4f2f;
|
||||||
|
font-weight: 800;
|
||||||
|
background: rgba(255, 239, 220, 0.72);
|
||||||
|
border: 1px solid rgba(190, 118, 72, 0.22);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 7px 9px;
|
||||||
|
}
|
||||||
.save-slot-title {
|
.save-slot-title {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #4f3928;
|
color: #4f3928;
|
||||||
|
|
@ -590,9 +621,12 @@
|
||||||
.save-slot-actions {
|
.save-slot-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.save-slot-list { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
.save-slot-actions .btn[disabled] {
|
.save-slot-actions .btn[disabled] {
|
||||||
opacity: 0.42;
|
opacity: 0.42;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
|
||||||
|
|
@ -230,12 +230,3 @@ body.touch-mobile-ui.mobile-mode-family .lineage-main-card { z-index: 9; }
|
||||||
.card.selected-card { --ui-theme-rgb: 204, 121, 167; background: linear-gradient(180deg, rgba(253, 245, 250, 0.94), rgba(249, 235, 244, 0.91)); border-color: rgba(204, 121, 167, 0.24); }
|
.card.selected-card { --ui-theme-rgb: 204, 121, 167; background: linear-gradient(180deg, rgba(253, 245, 250, 0.94), rgba(249, 235, 244, 0.91)); border-color: rgba(204, 121, 167, 0.24); }
|
||||||
.card.colony-card { --ui-theme-rgb: 0, 158, 115; background: linear-gradient(180deg, rgba(240, 250, 247, 0.94), rgba(229, 245, 239, 0.91)); border-color: rgba(0, 158, 115, 0.24); }
|
.card.colony-card { --ui-theme-rgb: 0, 158, 115; background: linear-gradient(180deg, rgba(240, 250, 247, 0.94), rgba(229, 245, 239, 0.91)); border-color: rgba(0, 158, 115, 0.24); }
|
||||||
.card.log-card { --ui-theme-rgb: 230, 159, 0; background: linear-gradient(180deg, rgba(255, 249, 236, 0.94), rgba(253, 242, 215, 0.91)); border-color: rgba(230, 159, 0, 0.24); }
|
.card.log-card { --ui-theme-rgb: 230, 159, 0; background: linear-gradient(180deg, rgba(255, 249, 236, 0.94), rgba(253, 242, 215, 0.91)); border-color: rgba(230, 159, 0, 0.24); }
|
||||||
.tool[data-tool="freeze"] { --tool-icon: none; }
|
|
||||||
.tool[data-tool="freeze"]::before {
|
|
||||||
content: "🧊";
|
|
||||||
background: none;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.tool[data-tool="freeze"]::after { content: "🧊"; background: none; font-size: 52px; right: -10px; left: auto; opacity: 0.08; display:flex; align-items:center; justify-content:flex-end; }
|
|
||||||
|
|
|
||||||
134
index.html
134
index.html
|
|
@ -5,11 +5,11 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>たりない観察</title>
|
<title>たりない観察</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Ctext y=%2752%27 font-size=%2752%27%3E%E3%81%9F%3C/text%3E%3C/svg%3E" />
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 64 64%27%3E%3Ctext y=%2752%27 font-size=%2752%27%3E%E3%81%9F%3C/text%3E%3C/svg%3E" />
|
||||||
<link rel="stylesheet" href="css/base.css?v=15.24.2" />
|
<link rel="stylesheet" href="css/base.css?v=15.24.14" />
|
||||||
<link rel="stylesheet" href="css/layout.css?v=15.24.2" />
|
<link rel="stylesheet" href="css/layout.css?v=15.24.14" />
|
||||||
<link rel="stylesheet" href="css/panel.css?v=15.24.2" />
|
<link rel="stylesheet" href="css/panel.css?v=15.24.14" />
|
||||||
<link rel="stylesheet" href="css/components.css?v=15.24.2" />
|
<link rel="stylesheet" href="css/components.css?v=15.24.14" />
|
||||||
<link rel="stylesheet" href="css/mobile.css?v=15.24.2" />
|
<link rel="stylesheet" href="css/mobile.css?v=15.24.14" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loadingScreen" class="loading-screen" aria-live="polite">
|
<div id="loadingScreen" class="loading-screen" aria-live="polite">
|
||||||
|
|
@ -183,67 +183,67 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
|
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
|
||||||
<script src="js/version.js?v=15.24.2" defer></script>
|
<script src="js/version.js?v=15.24.14" defer></script>
|
||||||
<script src="js/event_bus.js?v=15.24.2" defer></script>
|
<script src="js/event_bus.js?v=15.24.14" defer></script>
|
||||||
<script src="js/performance_manager.js?v=15.24.2" defer></script>
|
<script src="js/performance_manager.js?v=15.24.14" defer></script>
|
||||||
<script src="js/registry_base.js?v=15.24.2" defer></script>
|
<script src="js/registry_base.js?v=15.24.14" defer></script>
|
||||||
<script src="js/disease_registry.js?v=15.24.2" defer></script>
|
<script src="js/disease_registry.js?v=15.24.14" defer></script>
|
||||||
<script src="js/effect_registry.js?v=15.24.2" defer></script>
|
<script src="js/effect_registry.js?v=15.24.14" defer></script>
|
||||||
<script src="js/sound_pack.js?v=15.24.2" defer></script>
|
<script src="js/sound_pack.js?v=15.24.14" defer></script>
|
||||||
<script src="js/data.js?v=15.24.2" defer></script>
|
<script src="js/data.js?v=15.24.14" defer></script>
|
||||||
<script src="js/item_visual_registry.js?v=15.24.2" defer></script>
|
<script src="js/item_visual_registry.js?v=15.24.14" defer></script>
|
||||||
<script src="js/food_registry.js?v=15.24.2" defer></script>
|
<script src="js/food_registry.js?v=15.24.14" defer></script>
|
||||||
<script src="js/input_mode_manager.js?v=15.24.2" defer></script>
|
<script src="js/input_mode_manager.js?v=15.24.14" defer></script>
|
||||||
<script src="js/math.js?v=15.24.2" defer></script>
|
<script src="js/math.js?v=15.24.14" defer></script>
|
||||||
<script src="js/physics_helpers.js?v=15.24.2" defer></script>
|
<script src="js/physics_helpers.js?v=15.24.14" defer></script>
|
||||||
<script src="js/assets.js?v=15.24.2" defer></script>
|
<script src="js/assets.js?v=15.24.14" defer></script>
|
||||||
<script src="js/audio.js?v=15.24.2" defer></script>
|
<script src="js/audio.js?v=15.24.14" defer></script>
|
||||||
<script src="js/render.js?v=15.24.2" defer></script>
|
<script src="js/render.js?v=15.24.14" defer></script>
|
||||||
<script src="js/sim_core.js?v=15.24.2" defer></script>
|
<script src="js/sim_core.js?v=15.24.14" defer></script>
|
||||||
<script src="js/structures.js?v=15.24.2" defer></script>
|
<script src="js/structures.js?v=15.24.14" defer></script>
|
||||||
<script src="js/items.js?v=15.24.2" defer></script>
|
<script src="js/items.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ants.js?v=15.24.2" defer></script>
|
<script src="js/ants.js?v=15.24.14" defer></script>
|
||||||
<script src="js/health.js?v=15.24.2" defer></script>
|
<script src="js/health.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai.js?v=15.24.2" defer></script>
|
<script src="js/tarinai.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_identity_social.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_identity_social.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_action_state.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_action_state.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_disease_nest.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_disease_nest.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_item_effects.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_item_effects.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_needs_items.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_needs_items.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_social_move_life.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_social_move_life.js?v=15.24.14" defer></script>
|
||||||
<script src="js/tarinai_render.js?v=15.24.2" defer></script>
|
<script src="js/tarinai_render.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world.js?v=15.24.2" defer></script>
|
<script src="js/world.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_view.js?v=15.24.2" defer></script>
|
<script src="js/world_view.js?v=15.24.14" defer></script>
|
||||||
<script src="js/family_graph.js?v=15.24.2" defer></script>
|
<script src="js/family_graph.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_family_social.js?v=15.24.2" defer></script>
|
<script src="js/world_family_social.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_combat_effects.js?v=15.24.2" defer></script>
|
<script src="js/world_combat_effects.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_environment.js?v=15.24.2" defer></script>
|
<script src="js/world_environment.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_spatial_budget.js?v=15.24.2" defer></script>
|
<script src="js/world_spatial_budget.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_ants_system.js?v=15.24.2" defer></script>
|
<script src="js/world_ants_system.js?v=15.24.14" defer></script>
|
||||||
<script src="js/weather_system.js?v=15.24.2" defer></script>
|
<script src="js/weather_system.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_update.js?v=15.24.2" defer></script>
|
<script src="js/world_update.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_placement_log.js?v=15.24.2" defer></script>
|
<script src="js/world_placement_log.js?v=15.24.14" defer></script>
|
||||||
<script src="js/world_bootstrap.js?v=15.24.2" defer></script>
|
<script src="js/world_bootstrap.js?v=15.24.14" defer></script>
|
||||||
<script src="js/text_catalog.js?v=15.24.2" defer></script>
|
<script src="js/text_catalog.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui.js?v=15.24.2" defer></script>
|
<script src="js/ui.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_log.js?v=15.24.2" defer></script>
|
<script src="js/ui_log.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_helpers.js?v=15.24.2" defer></script>
|
<script src="js/ui_helpers.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_selected.js?v=15.24.2" defer></script>
|
<script src="js/ui_selected.js?v=15.24.14" defer></script>
|
||||||
<script src="js/snapshot_system.js?v=15.24.2" defer></script>
|
<script src="js/snapshot_system.js?v=15.24.14" defer></script>
|
||||||
<script src="js/save_system.js?v=15.24.2" defer></script>
|
<script src="js/save_system.js?v=15.24.14" defer></script>
|
||||||
<script src="js/patch_helpers.js?v=15.24.2" defer></script>
|
<script src="js/patch_helpers.js?v=15.24.14" defer></script>
|
||||||
<script src="js/freeze_system.js?v=15.24.2" defer></script>
|
<script src="js/freeze_system.js?v=15.24.14" defer></script>
|
||||||
<script src="js/relation_event_system.js?v=15.24.2" defer></script>
|
<script src="js/relation_event_system.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_layout_dialogs.js?v=15.24.2" defer></script>
|
<script src="js/ui_layout_dialogs.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_tools.js?v=15.24.2" defer></script>
|
<script src="js/ui_tools.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_bind.js?v=15.24.2" defer></script>
|
<script src="js/ui_bind.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_charts.js?v=15.24.2" defer></script>
|
<script src="js/ui_charts.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_family_data.js?v=15.24.2" defer></script>
|
<script src="js/ui_family_data.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_family_async.js?v=15.24.2" defer></script>
|
<script src="js/ui_family_async.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_family_layout.js?v=15.24.2" defer></script>
|
<script src="js/ui_family_layout.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_family_paths.js?v=15.24.2" defer></script>
|
<script src="js/ui_family_paths.js?v=15.24.14" defer></script>
|
||||||
<script src="js/ui_family_render.js?v=15.24.2" defer></script>
|
<script src="js/ui_family_render.js?v=15.24.14" defer></script>
|
||||||
<script src="js/main.js?v=15.24.2" defer></script>
|
<script src="js/main.js?v=15.24.14" defer></script>
|
||||||
<script src="js/debug_tools.js?v=15.24.2" defer></script>
|
<script src="js/debug_tools.js?v=15.24.14" defer></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
29
js/data.js
29
js/data.js
|
|
@ -50,10 +50,9 @@ const TOOL_DEFINITIONS = Object.freeze({
|
||||||
observe: { id: "observe", label: "\u89b3\u5bdf", placeable: false, scalable: false, icon: "assets/ui/tool_observe.webp", tooltip: "\u500b\u4f53\u3092\u9078\u629e\u3057\u3001\u72b6\u614b\u30fb\u6027\u683c\u30fb\u75c5\u6c17\u30fb\u304a\u6c17\u306b\u5165\u308a\u3092\u78ba\u8a8d\u3059\u308b\u3002" },
|
observe: { id: "observe", label: "\u89b3\u5bdf", placeable: false, scalable: false, icon: "assets/ui/tool_observe.webp", tooltip: "\u500b\u4f53\u3092\u9078\u629e\u3057\u3001\u72b6\u614b\u30fb\u6027\u683c\u30fb\u75c5\u6c17\u30fb\u304a\u6c17\u306b\u5165\u308a\u3092\u78ba\u8a8d\u3059\u308b\u3002" },
|
||||||
delete: { id: "delete", label: "\u524a\u9664", placeable: false, scalable: false, icon: "assets/ui/tool_delete.webp", tooltip: "\u7f6e\u3044\u305f\u9053\u5177\u3084\u6c5a\u308c\u3092\u6d88\u3059\u3002" },
|
delete: { id: "delete", label: "\u524a\u9664", placeable: false, scalable: false, icon: "assets/ui/tool_delete.webp", tooltip: "\u7f6e\u3044\u305f\u9053\u5177\u3084\u6c5a\u308c\u3092\u6d88\u3059\u3002" },
|
||||||
poke: { id: "poke", label: "\u3064\u3064\u304f", placeable: false, scalable: false, iconText: "\u{1F448}", tooltip: "\u305f\u308a\u306a\u3044\u3092\u3064\u3064\u304f\u3002\u30dc\u30fc\u30eb\u3082\u3064\u3064\u3044\u3066\u8ee2\u304c\u305b\u308b\u3002" },
|
poke: { id: "poke", label: "\u3064\u3064\u304f", placeable: false, scalable: false, iconText: "\u{1F448}", tooltip: "\u305f\u308a\u306a\u3044\u3092\u3064\u3064\u304f\u3002\u30dc\u30fc\u30eb\u3082\u3064\u3064\u3044\u3066\u8ee2\u304c\u305b\u308b\u3002" },
|
||||||
pinch: { id: "pinch", label: "\u3064\u307e\u3080", placeable: false, scalable: false, iconText: "\u{1F90F}", tooltip: "\u305f\u308a\u306a\u3044\u3084\u7269\u3092\u3064\u307e\u3093\u3067\u79fb\u52d5\u3055\u305b\u308b\u3002" },
|
pinch: { id: "pinch", label: "\u3064\u307e\u3080", placeable: false, scalable: false, iconText: "\u{1F90F}", tooltip: "\u305f\u308a\u306a\u3044\u3084\u7269\u3092\u79fb\u52d5\u3055\u305b\u308b\u3002\u51b7\u51cd\u5eab\u306b\u3082\u904b\u3079\u308b\u3002" },
|
||||||
new: { id: "new", label: "\u8ffd\u52a0", placeable: false, scalable: false, icon: "assets/ui/tool_new.webp", tooltip: "\u753b\u9762\u5916\u304b\u3089\u65b0\u3057\u3044\u305f\u308a\u306a\u3044\u3092\u547c\u3076\u3002" },
|
new: { id: "new", label: "\u8ffd\u52a0", placeable: false, scalable: false, icon: "assets/ui/tool_new.webp", tooltip: "\u753b\u9762\u5916\u304b\u3089\u65b0\u3057\u3044\u305f\u308a\u306a\u3044\u3092\u547c\u3076\u3002" },
|
||||||
water_hose: { id: "water_hose", label: "\u6d17\u6d44", placeable: false, scalable: false, iconText: "\u{1F6BF}", tooltip: "\u30c9\u30e9\u30c3\u30b0\u3067\u6c5a\u308c\u3092\u304d\u308c\u3044\u306b\u3059\u308b\u3002" },
|
water_hose: { id: "water_hose", label: "\u6d17\u6d44", placeable: false, scalable: false, iconText: "\u{1F6BF}", tooltip: "\u30c9\u30e9\u30c3\u30b0\u3067\u6c5a\u308c\u3092\u304d\u308c\u3044\u306b\u3059\u308b\u3002" },
|
||||||
freeze: { id: "freeze", label: "冷凍", placeable: false, scalable: false, iconText: "\u{1F9CA}", tooltip: "つまむでたりないを冷凍個体UIまで運ぶと冷凍保存する。" },
|
|
||||||
zunchi: { id: "zunchi", itemType: "zunchi", label: "\u305a\u3093\u3061", placeable: true, scalable: true, radius: 14, amount: 240, icon: "assets/ui/tool_zunchi.webp", tooltip: "\u305f\u308a\u306a\u3044\u306e\u305a\u3093\u3061\u3002\u75c5\u6c17\u3084\u8349\u306e\u80a5\u6599\u306b\u95a2\u308f\u308b\u3002" },
|
zunchi: { id: "zunchi", itemType: "zunchi", label: "\u305a\u3093\u3061", placeable: true, scalable: true, radius: 14, amount: 240, icon: "assets/ui/tool_zunchi.webp", tooltip: "\u305f\u308a\u306a\u3044\u306e\u305a\u3093\u3061\u3002\u75c5\u6c17\u3084\u8349\u306e\u80a5\u6599\u306b\u95a2\u308f\u308b\u3002" },
|
||||||
sweet: { id: "sweet", itemType: "sweet", label: "\u305a\u3093\u3060\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_sweet.webp", tooltip: "\u305f\u308a\u306a\u3044\u306e\u5927\u597d\u7269\u3002\u4e00\u90e8\u306e\u75c5\u6c17\u3092\u6cbb\u305b\u308b\u3002" },
|
sweet: { id: "sweet", itemType: "sweet", label: "\u305a\u3093\u3060\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_sweet.webp", tooltip: "\u305f\u308a\u306a\u3044\u306e\u5927\u597d\u7269\u3002\u4e00\u90e8\u306e\u75c5\u6c17\u3092\u6cbb\u305b\u308b\u3002" },
|
||||||
love_mochi: { id: "love_mochi", itemType: "love_mochi", label: "\u3078\u3053\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_love_mochi.webp", tooltip: "\u98df\u5f8c\u3057\u3070\u3089\u304f\u7e41\u6b96\u884c\u52d5\u304c\u8d77\u304d\u3084\u3059\u304f\u306a\u308b\u3002" },
|
love_mochi: { id: "love_mochi", itemType: "love_mochi", label: "\u3078\u3053\u9905", placeable: true, scalable: true, radius: 13, amount: 62, icon: "assets/ui/tool_love_mochi.webp", tooltip: "\u98df\u5f8c\u3057\u3070\u3089\u304f\u7e41\u6b96\u884c\u52d5\u304c\u8d77\u304d\u3084\u3059\u304f\u306a\u308b\u3002" },
|
||||||
|
|
@ -77,10 +76,11 @@ const TOOL_DEFINITIONS = Object.freeze({
|
||||||
nest_box: { id: "nest_box", itemType: "nest_box", label: "\u5de3\u7bb1", placeable: true, scalable: false, radius: 42, amount: 999, icon: "assets/ui/tool_nest_box.webp", collisionShape: "nest_box_3x3", tooltip: "\u3044\u308b\u3060\u3051\u3067\u30b9\u30c8\u30ec\u30b9\u4f4e\u6e1b\u3002\u7720\u308b\u3068\u7761\u7720\u306e\u8cea\u304c\u4e0a\u304c\u308b\u3002" },
|
nest_box: { id: "nest_box", itemType: "nest_box", label: "\u5de3\u7bb1", placeable: true, scalable: false, radius: 42, amount: 999, icon: "assets/ui/tool_nest_box.webp", collisionShape: "nest_box_3x3", tooltip: "\u3044\u308b\u3060\u3051\u3067\u30b9\u30c8\u30ec\u30b9\u4f4e\u6e1b\u3002\u7720\u308b\u3068\u7761\u7720\u306e\u8cea\u304c\u4e0a\u304c\u308b\u3002" },
|
||||||
ant_nest: { id: "ant_nest", itemType: "ant_nest", label: "\u30a2\u30ea\u306e\u5de3", placeable: true, scalable: false, radius: 17, amount: 999, icon: "assets/ui/tool_ant_nest.webp", collisionShape: "circle", tooltip: "\u50cd\u304d\u30a2\u30ea\u304c\u305f\u308a\u306a\u3044\u3092\u63a2\u3057\u3001\u5de3\u3078\u904b\u3076\u3002" },
|
ant_nest: { id: "ant_nest", itemType: "ant_nest", label: "\u30a2\u30ea\u306e\u5de3", placeable: true, scalable: false, radius: 17, amount: 999, icon: "assets/ui/tool_ant_nest.webp", collisionShape: "circle", tooltip: "\u50cd\u304d\u30a2\u30ea\u304c\u305f\u308a\u306a\u3044\u3092\u63a2\u3057\u3001\u5de3\u3078\u904b\u3076\u3002" },
|
||||||
ball: { id: "ball", itemType: "ball", label: "\u30dc\u30fc\u30eb", placeable: true, scalable: false, radius: 18, amount: 999, icon: "assets/ui/tool_ball.webp", collisionShape: "circle", tooltip: "\u305f\u308a\u306a\u3044\u304c\u904a\u3076\u305f\u3081\u306e\u304a\u3082\u3061\u3083\u3002" },
|
ball: { id: "ball", itemType: "ball", label: "\u30dc\u30fc\u30eb", placeable: true, scalable: false, radius: 18, amount: 999, icon: "assets/ui/tool_ball.webp", collisionShape: "circle", tooltip: "\u305f\u308a\u306a\u3044\u304c\u904a\u3076\u305f\u3081\u306e\u304a\u3082\u3061\u3083\u3002" },
|
||||||
signboard: { id: "signboard", itemType: "signboard", label: "\u770b\u677f", placeable: true, scalable: false, radius: 24, amount: 999, iconText: "\u{1FAA7}", collisionShape: "circle", tooltip: "\u77ed\u3044\u30c6\u30ad\u30b9\u30c8\u3092\u66f8\u304d\u8fbc\u3081\u308b\u770b\u677f\u3002\u305f\u308a\u306a\u3044\u306b\u5bfe\u3059\u308b\u52b9\u679c\u306f\u7121\u3057" },
|
signboard: { id: "signboard", itemType: "signboard", label: "\u770b\u677f", placeable: true, scalable: false, radius: 30, amount: 999, icon: "assets/ui/tool_signboard.webp", collisionShape: "circle", tooltip: "\u8a2d\u7f6e\u5f8c\u306b\u89b3\u5bdf\u30c4\u30fc\u30eb\u3067\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u6587\u5b57\u3092\u66f8\u304d\u8fbc\u3081\u308b\u770b\u677f\u30026文字×3行まで\u3002" },
|
||||||
|
duplicator: { id: "duplicator", itemType: "duplicator", label: "\u8907\u88fd\u6a5f", placeable: true, scalable: false, radius: 34, amount: 999, icon: "assets/ui/tool_duplicator.webp", collisionShape: "circle", tooltip: "食べ物や薬をセットすると、自動供給してくれる。" },
|
||||||
firecracker: { id: "firecracker", itemType: "firecracker", label: "\u7206\u7af9", placeable: true, scalable: true, radius: 15, amount: 999, icon: "assets/ui/tool_firecracker.webp", tooltip: "\u7206\u767a\u3092\u8d77\u3053\u3059\u3002" },
|
firecracker: { id: "firecracker", itemType: "firecracker", label: "\u7206\u7af9", placeable: true, scalable: true, radius: 15, amount: 999, icon: "assets/ui/tool_firecracker.webp", tooltip: "\u7206\u767a\u3092\u8d77\u3053\u3059\u3002" },
|
||||||
pushpin: { id: "pushpin", itemType: "pushpin", label: "\u753b\u92f2", placeable: true, scalable: false, radius: 8, amount: 999, icon: "assets/ui/tool_pushpin.webp", tooltip: "\u843d\u3068\u3059\u3068\u3053\u308d\u304c\u308a\u3001\u305f\u308a\u306a\u3044\u306b\u523a\u3055\u308b\u3068\u30d1\u30cb\u30c3\u30af\u3068\u7d99\u7d9a\u30c0\u30e1\u30fc\u30b8\u3092\u4e0e\u3048\u308b\u3002" },
|
pushpin: { id: "pushpin", itemType: "pushpin", label: "\u753b\u92f2", placeable: true, scalable: false, radius: 8, amount: 999, icon: "assets/ui/tool_pushpin.webp", tooltip: "\u843d\u3068\u3059\u3068\u3053\u308d\u304c\u308a\u3001\u305f\u308a\u306a\u3044\u306b\u523a\u3055\u308b\u3068\u30d1\u30cb\u30c3\u30af\u3068\u7d99\u7d9a\u30c0\u30e1\u30fc\u30b8\u3092\u4e0e\u3048\u308b\u3002" },
|
||||||
oshibyo: { id: "oshibyo", itemType: "oshibyo", label: "おしり鋲", placeable: true, scalable: false, radius: 9, amount: 999, icon: "assets/objects/oshibyo.webp", tooltip: "刺さるとずんちが出なくなる。つまんで抜くと溜まったずんちが弾け飛ぶ。" },
|
oshibyo: { id: "oshibyo", itemType: "oshibyo", label: "\u304a\u3057\u308a\u92f2", placeable: true, scalable: false, radius: 9, amount: 999, icon: "assets/ui/tool_oshibyo.webp", tooltip: "\u523a\u3055\u308b\u3068\u305a\u3093\u3061\u304c\u51fa\u306a\u304f\u306a\u308b\u3002\u3064\u307e\u3093\u3067\u629c\u304f\u3068\u6e9c\u307e\u3063\u305f\u305a\u3093\u3061\u304c\u5f3e\u3051\u98db\u3076\u3002" },
|
||||||
fence_v: { id: "fence_v", itemType: "fence_v", label: "\u7e26\u306e\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_v.webp", collisionShape: "rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u3002" },
|
fence_v: { id: "fence_v", itemType: "fence_v", label: "\u7e26\u306e\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_v.webp", collisionShape: "rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u3002" },
|
||||||
fence_h: { id: "fence_h", itemType: "fence_h", label: "\u6a2a\u306e\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_h.webp", collisionShape: "rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u3002" },
|
fence_h: { id: "fence_h", itemType: "fence_h", label: "\u6a2a\u306e\u67f5", placeable: true, scalable: true, radius: 42, amount: 999, icon: "assets/ui/tool_fence_h.webp", collisionShape: "rect", tooltip: "\u305f\u308a\u306a\u3044\u3092\u901a\u305b\u3093\u307c\u3059\u308b\u3002" },
|
||||||
water: { id: "water", itemType: "water", label: "\u6c34", placeable: false, scalable: false, radius: 12, amount: 64, simulationOnly: true },
|
water: { id: "water", itemType: "water", label: "\u6c34", placeable: false, scalable: false, radius: 12, amount: 64, simulationOnly: true },
|
||||||
|
|
@ -92,13 +92,13 @@ const TOOL_DEFINITIONS = Object.freeze({
|
||||||
|
|
||||||
|
|
||||||
const ITEM_TRAITS = Object.freeze({
|
const ITEM_TRAITS = Object.freeze({
|
||||||
obstacle: new Set(["stone", "ball", "nest_box", "bed", "fence_v", "fence_h"]),
|
obstacle: new Set(["stone", "ball", "nest_box", "bed", "duplicator", "fence_v", "fence_h"]),
|
||||||
food_interest: new Set(["sweet", "love_mochi", "fight_mochi", "grass", "water", "water_bowl", "ant_corpse", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice", "sleep_drug"]),
|
food_interest: new Set(["sweet", "love_mochi", "fight_mochi", "grass", "water", "water_bowl", "ant_corpse", "duplicator", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice", "sleep_drug"]),
|
||||||
hazard: new Set(["firecracker", "genkotsu", "pushpin", "oshibyo", "sleep_drug", "zunchi", "splat", "mystery_drug", "laxative", "niteropu", "mercury", "giant_drug", "dwarf_drug"]),
|
hazard: new Set(["firecracker", "genkotsu", "pushpin", "sleep_drug", "zunchi", "splat", "mystery_drug", "laxative", "niteropu", "mercury", "giant_drug", "dwarf_drug"]),
|
||||||
pin: new Set(["pushpin", "oshibyo"]),
|
pin: new Set(["pushpin", "oshibyo"]),
|
||||||
kinematic: new Set(["ball", "pushpin", "oshibyo", "zunchi"]),
|
kinematic: new Set(["ball", "pushpin", "oshibyo", "zunchi"]),
|
||||||
sleepFurniture: new Set(["bed", "nest_box"]),
|
sleepFurniture: new Set(["bed", "nest_box"]),
|
||||||
draggable: new Set(["ball", "stone", "bed", "nest_box", "signboard", "pushpin", "oshibyo"]),
|
draggable: new Set(["ball", "stone", "bed", "nest_box", "signboard", "duplicator", "pushpin", "oshibyo"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
function itemHasTrait(type = "", trait = "") {
|
function itemHasTrait(type = "", trait = "") {
|
||||||
|
|
@ -136,9 +136,8 @@ const TOOL_CATEGORIES = Object.freeze([
|
||||||
{ id: "operate", label: "\u64cd\u4f5c\u30fb\u89b3\u5bdf", themeClass: "tool-category-operate", order: 10, toolIds: ["observe", "delete", "poke", "pinch", "new", "water_hose"] },
|
{ id: "operate", label: "\u64cd\u4f5c\u30fb\u89b3\u5bdf", themeClass: "tool-category-operate", order: 10, toolIds: ["observe", "delete", "poke", "pinch", "new", "water_hose"] },
|
||||||
{ id: "food", label: "\u98df\u3079\u7269\u30fb\u6c34\u30fb\u8349", themeClass: "tool-category-food", order: 20, toolIds: ["sweet", "love_mochi", "fight_mochi", "grass", "water_bowl", "zunda_juice"] },
|
{ id: "food", label: "\u98df\u3079\u7269\u30fb\u6c34\u30fb\u8349", themeClass: "tool-category-food", order: 20, toolIds: ["sweet", "love_mochi", "fight_mochi", "grass", "water_bowl", "zunda_juice"] },
|
||||||
{ id: "medicine", label: "\u85ac\u30fb\u500b\u4f53\u52b9\u679c", themeClass: "tool-category-medicine", order: 30, toolIds: ["sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug"] },
|
{ id: "medicine", label: "\u85ac\u30fb\u500b\u4f53\u52b9\u679c", themeClass: "tool-category-medicine", order: 30, toolIds: ["sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug"] },
|
||||||
{ id: "habitat", label: "\u751f\u6d3b\u30fb\u751f\u304d\u7269", themeClass: "tool-category-habitat", order: 40, toolIds: ["zunchi", "bed", "nest_box", "ant_nest", "ball", "signboard"] },
|
{ id: "habitat", label: "\u751f\u6d3b\u30fb\u751f\u304d\u7269", themeClass: "tool-category-habitat", order: 40, toolIds: ["zunchi", "bed", "nest_box", "ant_nest", "ball", "signboard", "duplicator"] },
|
||||||
{ id: "hazard", label: "\u5371\u967a\u30fb\u969c\u5bb3\u7269", themeClass: "tool-category-hazard", order: 50, toolIds: ["stone", "genkotsu", "firecracker", "pushpin", "oshibyo", "fence_v", "fence_h"] },
|
{ id: "hazard", label: "\u5371\u967a\u30fb\u969c\u5bb3\u7269", themeClass: "tool-category-hazard", order: 50, toolIds: ["stone", "genkotsu", "firecracker", "pushpin", "oshibyo", "fence_v", "fence_h"] },
|
||||||
{ id: "freeze", label: "冷凍", themeClass: "tool-category-freeze", order: 99, toolIds: ["freeze"] },
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function toolCategories() {
|
function toolCategories() {
|
||||||
|
|
@ -244,7 +243,7 @@ const CONFIG = {
|
||||||
splatLimit: 48,
|
splatLimit: 48,
|
||||||
effectLimit: 78,
|
effectLimit: 78,
|
||||||
worldPadding: 30,
|
worldPadding: 30,
|
||||||
hungerPerMinute: 11.2,
|
hungerPerMinute: 36.0,
|
||||||
lonelinessPerMinute: 7.6,
|
lonelinessPerMinute: 7.6,
|
||||||
energyPerMinute: 8.2,
|
energyPerMinute: 8.2,
|
||||||
stressDecayPerMinute: 10.2,
|
stressDecayPerMinute: 10.2,
|
||||||
|
|
@ -256,14 +255,14 @@ const CONFIG = {
|
||||||
weatherChangeMin: 46,
|
weatherChangeMin: 46,
|
||||||
weatherChangeMax: 96,
|
weatherChangeMax: 96,
|
||||||
bubbleLimit: 32,
|
bubbleLimit: 32,
|
||||||
grassLimit: 96,
|
grassLimit: 180,
|
||||||
highPopulationMode: 70,
|
highPopulationMode: 70,
|
||||||
ultraPopulationMode: 120,
|
ultraPopulationMode: 120,
|
||||||
};
|
};
|
||||||
const FIELD_TYPES = {
|
const FIELD_TYPES = {
|
||||||
cage: { id: "cage", label: "\u30b1\u30fc\u30b8", sizeLabel: "\u5c0f", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 0.46, population: 12, grass: 9 },
|
cage: { id: "cage", label: "\u30b1\u30fc\u30b8", sizeLabel: "\u5c0f", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 0.46, population: 12, grass: 16 },
|
||||||
garden: { id: "garden", label: "\u5ead", sizeLabel: "\u4e2d", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 1.0, population: CONFIG.initialPopulation, grass: 18 },
|
garden: { id: "garden", label: "\u5ead", sizeLabel: "\u4e2d", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 1.0, population: CONFIG.initialPopulation, grass: 34 },
|
||||||
park: { id: "park", label: "\u516c\u5712", sizeLabel: "\u5927", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 2.35, population: 24, grass: 28 },
|
park: { id: "park", label: "\u516c\u5712", sizeLabel: "\u5927", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 2.35, population: 24, grass: 62 },
|
||||||
};
|
};
|
||||||
const LIGHTING_TUNING = {
|
const LIGHTING_TUNING = {
|
||||||
rayAlpha: 0.040,
|
rayAlpha: 0.040,
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,10 @@
|
||||||
tarinai.loveMochiTimer = Math.max(tarinai.loveMochiTimer || 0, 42 * scale + rand(4, 12));
|
tarinai.loveMochiTimer = Math.max(tarinai.loveMochiTimer || 0, 42 * scale + rand(4, 12));
|
||||||
const nutrition = Number(context.nutrition) || 0;
|
const nutrition = Number(context.nutrition) || 0;
|
||||||
if (typeof applyNeedRelief === "function") applyNeedRelief(tarinai, { fulfill: -(context.source === "eating" ? nutrition * 0.5 : 12), social: -4 });
|
if (typeof applyNeedRelief === "function") applyNeedRelief(tarinai, { fulfill: -(context.source === "eating" ? nutrition * 0.5 : 12), social: -4 });
|
||||||
|
if (context.source === "eating") {
|
||||||
|
if (tarinai.forceBehavior) tarinai.forceBehavior("approach_mate", { source: "love_mochi", priority: 150, ttl: 24, searchRange: 820, reasonText: "へこ餅で繁殖できる相手を探している" });
|
||||||
|
else if (typeof queueForcedTarinaiBehavior === "function") queueForcedTarinaiBehavior(tarinai, "approach_mate", { source: "love_mochi", priority: 150, ttl: 24, searchRange: 820, reasonText: "へこ餅で繁殖できる相手を探している" });
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -119,7 +123,11 @@
|
||||||
tarinai.fightMochiTimer = Math.max(tarinai.fightMochiTimer || 0, 52 * scale + rand(10, 18));
|
tarinai.fightMochiTimer = Math.max(tarinai.fightMochiTimer || 0, 52 * scale + rand(10, 18));
|
||||||
const nutrition = Number(context.nutrition) || 0;
|
const nutrition = Number(context.nutrition) || 0;
|
||||||
if (typeof applyNeedShock === "function") applyNeedShock(tarinai, { safety: context.source === "eating" ? nutrition * 0.5 : 16 });
|
if (typeof applyNeedShock === "function") applyNeedShock(tarinai, { safety: context.source === "eating" ? nutrition * 0.5 : 16 });
|
||||||
if (context.source === "eating") tarinai.fearTimer = Math.max(tarinai.fearTimer || 0, 0.32);
|
if (context.source === "eating") {
|
||||||
|
tarinai.fearTimer = Math.max(tarinai.fearTimer || 0, 0.32);
|
||||||
|
if (tarinai.forceBehavior) tarinai.forceBehavior("fight_rival", { source: "fight_mochi", priority: 180, ttl: 24, searchRange: 820, reasonText: "けんか餅で気に入らない相手を探している" });
|
||||||
|
else if (typeof queueForcedTarinaiBehavior === "function") queueForcedTarinaiBehavior(tarinai, "fight_rival", { source: "fight_mochi", priority: 180, ttl: 24, searchRange: 820, reasonText: "けんか餅で気に入らない相手を探している" });
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -133,6 +141,8 @@
|
||||||
tarinai.fightMochiTimer = Math.max(0, (tarinai.fightMochiTimer || 0) - 2.5);
|
tarinai.fightMochiTimer = Math.max(0, (tarinai.fightMochiTimer || 0) - 2.5);
|
||||||
const baseChance = Math.min(0.98, 0.35 + nutrition * 0.075);
|
const baseChance = Math.min(0.98, 0.35 + nutrition * 0.075);
|
||||||
const sleepChance = tarinai.diseaseChance ? tarinai.diseaseChance(baseChance) : baseChance;
|
const sleepChance = tarinai.diseaseChance ? tarinai.diseaseChance(baseChance) : baseChance;
|
||||||
|
if (tarinai.forceBehavior) tarinai.forceBehavior("sleep_anywhere", { source: "sleep_drug", priority: 145, ttl: 14, duration: 8, minDuration: 8, reasonText: "ねむり薬で眠ろうとしている" });
|
||||||
|
else if (typeof queueForcedTarinaiBehavior === "function") queueForcedTarinaiBehavior(tarinai, "sleep_anywhere", { source: "sleep_drug", priority: 145, ttl: 14, duration: 8, minDuration: 8, reasonText: "ねむり薬で眠ろうとしている" });
|
||||||
if (!tarinai.sleepDisease && Math.random() < Math.min(0.98, sleepChance)) return tarinai.infectSleepDisease?.(context.item || null) || true;
|
if (!tarinai.sleepDisease && Math.random() < Math.min(0.98, sleepChance)) return tarinai.infectSleepDisease?.(context.item || null) || true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,24 @@
|
||||||
|
|
||||||
(function (global) {
|
(function (global) {
|
||||||
const rawDefinitions = {
|
const rawDefinitions = {
|
||||||
food: { label: "\u98df\u3079\u7269", nutrition: 8.5, hungerRelief: 0.95, interest: "normal", decayRateMultiplier: 1.0, hygienePenalty: 0.055 },
|
food: { label: "\u98df\u3079\u7269", nutrition: 18.0, hungerRelief: 1.0, interest: "normal", decayRateMultiplier: 1.0, hygienePenalty: 0.055 },
|
||||||
sweet: { label: "\u305a\u3093\u3060\u9905", nutrition: 7.2, hungerRelief: 0.75, interest: "zunda_high", decayRateMultiplier: 0.82, hygienePenalty: 0.055, cures: ["disease_explosion", "disease_fight"] },
|
sweet: { label: "\u305a\u3093\u3060\u9905", nutrition: 16.5, hungerRelief: 0.95, interest: "zunda_high", decayRateMultiplier: 0.82, hygienePenalty: 0.055, cures: ["disease_explosion", "disease_fight"] },
|
||||||
love_mochi: { label: "\u3078\u3053\u9905", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "love_mochi" },
|
love_mochi: { label: "\u3078\u3053\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "love_mochi" },
|
||||||
fight_mochi: { label: "\u3051\u3093\u304b\u9905", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "fight_mochi" },
|
fight_mochi: { label: "\u3051\u3093\u304b\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "fight_mochi" },
|
||||||
sleep_drug: { label: "\u306d\u3080\u308a\u85ac", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "sleep_drug" },
|
sleep_drug: { label: "\u306d\u3080\u308a\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "sleep_drug" },
|
||||||
laxative: { label: "\u4e0b\u5264", nutrition: 6.8, hungerRelief: 0, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "laxative", passThrough: true },
|
laxative: { label: "\u4e0b\u5264", nutrition: 6.8, hungerRelief: 0, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "laxative", passThrough: true },
|
||||||
protein: { label: "\u30d7\u30ed\u30c6\u30a4\u30f3", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "protein" },
|
protein: { label: "\u30d7\u30ed\u30c6\u30a4\u30f3", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "protein" },
|
||||||
niteropu: { label: "\u30f3\u30a4\u30c6\u30ed\u30d7", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "niteropu" },
|
niteropu: { label: "\u30f3\u30a4\u30c6\u30ed\u30d7", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "niteropu" },
|
||||||
ammo: { label: "\u5f3e\u85ac", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "ammo" },
|
ammo: { label: "\u5f3e\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "ammo" },
|
||||||
mystery_drug: { label: "\u602a\u3057\u3044\u85ac", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mystery_drug" },
|
mystery_drug: { label: "\u602a\u3057\u3044\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mystery_drug" },
|
||||||
mercury: { label: "\u6c34\u9280", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mercury" },
|
mercury: { label: "\u6c34\u9280", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mercury" },
|
||||||
giant_drug: { label: "\u5de8\u5927\u85ac", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "giant_drug" },
|
giant_drug: { label: "\u5de8\u5927\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "giant_drug" },
|
||||||
dwarf_drug: { label: "\u77ee\u5c0f\u85ac", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "dwarf_drug" },
|
dwarf_drug: { label: "\u77ee\u5c0f\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "dwarf_drug" },
|
||||||
zunda_juice: { label: "\u305a\u3093\u3060\u6c41", nutrition: 6.8, hungerRelief: 0.82, interest: "medicine_low", decayRateMultiplier: 1.12, hygienePenalty: 0.055, effectId: "zunda_juice", removesItemEffects: true },
|
zunda_juice: { label: "\u305a\u3093\u3060\u6c41", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.12, hygienePenalty: 0.055, effectId: "zunda_juice", removesItemEffects: true },
|
||||||
grass: { label: "\u8349", nutrition: 0.46, hungerRelief: 0.46, interest: "grass_like", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
grass: { label: "\u8349", nutrition: 2.6, hungerRelief: 0.55, interest: "grass_like", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
||||||
water: { label: "\u6c34", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
water: { label: "\u6c34", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
||||||
water_bowl: { label: "\u6c34\u306e\u76bf", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
water_bowl: { label: "\u6c34\u306e\u76bf", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 },
|
||||||
ant_corpse: { label: "\u30a2\u30ea\u306e\u6b7b\u9ab8", nutrition: 0.62, hungerRelief: 0.62, interest: "corpse", decayRateMultiplier: 1.0, hygienePenalty: 0.065 }
|
ant_corpse: { label: "\u30a2\u30ea\u306e\u6b7b\u9ab8", nutrition: 4.2, hungerRelief: 0.62, interest: "corpse", decayRateMultiplier: 1.0, hygienePenalty: 0.065 }
|
||||||
};
|
};
|
||||||
|
|
||||||
class FoodRegistry extends global.TarinaiRegistryBase {
|
class FoodRegistry extends global.TarinaiRegistryBase {
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,19 @@
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
style.id = "freezeSystemStyles";
|
style.id = "freezeSystemStyles";
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
.frozen-panel { margin-top: 10px; padding: 10px; border: 1px solid rgba(82, 63, 43, 0.18); border-radius: 14px; background: rgba(244, 249, 255, 0.72); }
|
.frozen-panel { grid-column: 1 / -1; margin-top: 8px; padding: 7px 8px; border: 1px solid rgba(82, 63, 43, 0.18); border-radius: 14px; background: rgba(244, 249, 255, 0.72); min-width: 0; }
|
||||||
.frozen-panel-title { display:flex; align-items:center; justify-content:space-between; gap:8px; font-weight:700; font-size:13px; color:#4f5f70; margin-bottom:8px; }
|
.frozen-panel-title { display:flex; align-items:center; justify-content:space-between; gap:8px; font-weight:800; font-size:12px; color:#4f5f70; margin-bottom:5px; }
|
||||||
|
.frozen-panel-toggle { width:100%; border:0; background:transparent; color:inherit; font:inherit; display:flex; align-items:center; justify-content:space-between; gap:8px; padding:0; cursor:pointer; text-align:left; }
|
||||||
|
.frozen-panel-toggle::after { content:"折り畳み"; font-size:11px; opacity:.62; font-weight:700; }
|
||||||
|
.frozen-panel.collapsed .frozen-panel-toggle::after { content:"展開"; }
|
||||||
.frozen-panel-count { font-size:12px; opacity:.72; }
|
.frozen-panel-count { font-size:12px; opacity:.72; }
|
||||||
.frozen-list { display:grid; grid-template-columns: 1fr; gap:7px; }
|
.frozen-list { display:flex; gap:6px; overflow-x:auto; padding-bottom:2px; }
|
||||||
.frozen-card { display:flex; align-items:center; gap:8px; width:100%; border:1px solid rgba(72, 98, 124, .20); border-radius:12px; padding:7px; background:rgba(255,255,255,.78); cursor:grab; color:#354657; text-align:left; box-shadow: 0 2px 7px rgba(41,53,65,.06); }
|
.frozen-panel.collapsed .frozen-list { display:none; }
|
||||||
|
.frozen-panel.collapsed { padding-bottom:7px; }
|
||||||
|
.frozen-card { display:flex; align-items:center; gap:6px; flex:0 0 138px; min-width:138px; max-width:168px; border:1px solid rgba(72, 98, 124, .20); border-radius:11px; padding:5px 6px; background:rgba(255,255,255,.78); cursor:grab; color:#354657; text-align:left; box-shadow: 0 2px 7px rgba(41,53,65,.06); }
|
||||||
.frozen-card:active { cursor:grabbing; }
|
.frozen-card:active { cursor:grabbing; }
|
||||||
.frozen-card.pending-thaw { outline:2px solid rgba(70, 142, 210, .46); background:rgba(227, 242, 255, .86); }
|
.frozen-card.pending-thaw { outline:2px solid rgba(70, 142, 210, .46); background:rgba(227, 242, 255, .86); }
|
||||||
.frozen-card img { width:34px; height:34px; object-fit:contain; flex:none; filter: drop-shadow(0 2px 3px rgba(34,44,55,.14)); }
|
.frozen-card img { width:26px; height:26px; object-fit:contain; flex:none; filter: drop-shadow(0 2px 3px rgba(34,44,55,.14)); }
|
||||||
.frozen-card-main { min-width:0; flex:1; }
|
.frozen-card-main { min-width:0; flex:1; }
|
||||||
.frozen-card-name { display:block; font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
.frozen-card-name { display:block; font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||||
.frozen-card-meta { display:block; font-size:11px; opacity:.70; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
.frozen-card-meta { display:block; font-size:11px; opacity:.70; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||||
|
|
@ -69,11 +74,21 @@
|
||||||
panel.id = "frozenPanel";
|
panel.id = "frozenPanel";
|
||||||
panel.className = "frozen-panel";
|
panel.className = "frozen-panel";
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="frozen-panel-title"><span>冷凍個体</span><span id="frozenPanelCount" class="frozen-panel-count">0</span></div>
|
<div class="frozen-panel-title"><button id="frozenPanelToggle" class="frozen-panel-toggle" type="button" aria-expanded="false"><span>\u51b7\u51cd\u5eab</span><span id="frozenPanelCount" class="frozen-panel-count">0</span></button></div>
|
||||||
<div id="frozenList" class="frozen-list"></div>
|
<div id="frozenList" class="frozen-list"></div>
|
||||||
`;
|
`;
|
||||||
const anchor = global.ui?.toolPalette || document.getElementById("toolPalette");
|
const storedCollapse = global.localStorage?.getItem("tarinaiFrozenPanelCollapsed");
|
||||||
if (anchor?.parentNode) anchor.parentNode.insertBefore(panel, anchor.nextSibling);
|
const collapsedByDefault = storedCollapse !== "0";
|
||||||
|
if (collapsedByDefault) {
|
||||||
|
panel.classList.add("collapsed");
|
||||||
|
panel.querySelector("#frozenPanelToggle")?.setAttribute("aria-expanded", "false");
|
||||||
|
} else {
|
||||||
|
panel.querySelector("#frozenPanelToggle")?.setAttribute("aria-expanded", "true");
|
||||||
|
}
|
||||||
|
const operateBody = document.querySelector('[data-tool-category="operate"] .tool-category-body');
|
||||||
|
const anchor = operateBody || global.ui?.toolPalette || document.getElementById("toolPalette");
|
||||||
|
if (operateBody) operateBody.appendChild(panel);
|
||||||
|
else if (anchor?.parentNode) anchor.parentNode.insertBefore(panel, anchor.nextSibling);
|
||||||
else document.body.appendChild(panel);
|
else document.body.appendChild(panel);
|
||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
@ -86,17 +101,17 @@
|
||||||
if (countEl) countEl.textContent = `${list.length}`;
|
if (countEl) countEl.textContent = `${list.length}`;
|
||||||
if (!listEl) return;
|
if (!listEl) return;
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
listEl.innerHTML = `<div class="frozen-empty">冷凍中のたりないはいません。</div>`;
|
listEl.innerHTML = `<div class="frozen-empty">\u51b7\u51cd\u4e2d\u306e\u305f\u308a\u306a\u3044\u306f\u3044\u307e\u305b\u3093\u3002\u51b7\u51cd\u3057\u305f\u305f\u308a\u306a\u3044\u306f\u30ea\u30bb\u30c3\u30c8\u3092\u7121\u8996\u3057\u3066\u4fdd\u5b58\u3067\u304d\u307e\u3059\u3002</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listEl.innerHTML = list.map(entry => {
|
listEl.innerHTML = list.map(entry => {
|
||||||
const data = entry.data || entry;
|
const data = entry.data || entry;
|
||||||
const losses = Array.isArray(entry.pendingLosses) && entry.pendingLosses.length ? ` / 訃報${entry.pendingLosses.length}` : "";
|
const losses = Array.isArray(entry.pendingLosses) && entry.pendingLosses.length ? ` / \u8a03\u5831${entry.pendingLosses.length}` : "";
|
||||||
const gen = data.generation ? `第${data.generation}世代` : "";
|
const gen = data.generation ? `\u7b2c${data.generation}\u4e16\u4ee3` : "";
|
||||||
const id = htmlEscape(entry.freezeId || entry.id || data.familyKey || "");
|
const id = htmlEscape(entry.freezeId || entry.id || data.familyKey || "");
|
||||||
return `<button class="frozen-card" type="button" draggable="true" data-frozen-id="${id}" title="ドラッグしてフィールドに戻す">
|
return `<button class="frozen-card" type="button" draggable="true" data-frozen-id="${id}" title="\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30d5\u30a3\u30fc\u30eb\u30c9\u306b\u623b\u3059">
|
||||||
<img src="${htmlEscape(spritePathFor(data.type || "smile"))}" alt="">
|
<img src="${htmlEscape(spritePathFor(data.type || "smile"))}" alt="">
|
||||||
<span class="frozen-card-main"><span class="frozen-card-name">${htmlEscape(data.name || "たりない")}</span><span class="frozen-card-meta">${htmlEscape(gen)}${htmlEscape(losses)}</span></span>
|
<span class="frozen-card-main"><span class="frozen-card-name">${htmlEscape(data.name || "\u305f\u308a\u306a\u3044")}</span><span class="frozen-card-meta">${htmlEscape(gen)}${htmlEscape(losses)}</span></span>
|
||||||
</button>`;
|
</button>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +127,7 @@
|
||||||
freezeId: id,
|
freezeId: id,
|
||||||
frozenAt: Date.now(),
|
frozenAt: Date.now(),
|
||||||
worldTime: worldRef?.time || 0,
|
worldTime: worldRef?.time || 0,
|
||||||
name: t.name || "たりない",
|
name: t.name || "\u305f\u308a\u306a\u3044",
|
||||||
familyKey: t.familyKey || "",
|
familyKey: t.familyKey || "",
|
||||||
liveId: t.id || "",
|
liveId: t.id || "",
|
||||||
liveToken: t.liveToken || 0,
|
liveToken: t.liveToken || 0,
|
||||||
|
|
@ -188,7 +203,7 @@
|
||||||
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination() : null, reason: `${unique[0]?.name || "誰か"}がいないことに気づいた`, wake: true });
|
t.setActionState?.("panic", { target: t.panicDestination ? t.panicDestination() : null, reason: `${unique[0]?.name || "誰か"}がいないことに気づいた`, wake: true });
|
||||||
}
|
}
|
||||||
t.bubble?.("いない", 1.1, "rgba(60,50,68,0.82)");
|
t.bubble?.("いない", 1.1, "rgba(60,50,68,0.82)");
|
||||||
t.recordChangeCause?.("冷凍中の訃報", "ストレス", { value: stress });
|
t.recordChangeCause?.("冷凍中の\u8a03\u5831", "ストレス", { value: stress });
|
||||||
t.addRecord?.(`冷凍中に${unique.map(l => l.name || "誰か").slice(0, 3).join("、")}が死亡していたことに気づいた。`, "death");
|
t.addRecord?.(`冷凍中に${unique.map(l => l.name || "誰か").slice(0, 3).join("、")}が死亡していたことに気づいた。`, "death");
|
||||||
worldRef.log?.(`${t.name}は解凍時に身近な個体の死に気づき、パニックになった。`, "death", { participants: [t] });
|
worldRef.log?.(`${t.name}は解凍時に身近な個体の死に気づき、パニックになった。`, "death", { participants: [t] });
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +272,14 @@
|
||||||
});
|
});
|
||||||
panel.addEventListener("dragend", () => global.document?.querySelector("canvas")?.classList.remove("freeze-drop-ready"));
|
panel.addEventListener("dragend", () => global.document?.querySelector("canvas")?.classList.remove("freeze-drop-ready"));
|
||||||
panel.addEventListener("click", (e) => {
|
panel.addEventListener("click", (e) => {
|
||||||
|
const toggle = e.target.closest("#frozenPanelToggle");
|
||||||
|
if (toggle) {
|
||||||
|
const collapsed = panel.classList.toggle("collapsed");
|
||||||
|
toggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
|
||||||
|
try { global.localStorage?.setItem("tarinaiFrozenPanelCollapsed", collapsed ? "1" : "0"); } catch (_) {}
|
||||||
|
global.audio?.uiFold?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const card = e.target.closest(".frozen-card[data-frozen-id]");
|
const card = e.target.closest(".frozen-card[data-frozen-id]");
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
pendingThawId = card.dataset.frozenId || "";
|
pendingThawId = card.dataset.frozenId || "";
|
||||||
|
|
@ -312,10 +335,6 @@
|
||||||
const originalHandleClick = proto.handleClick;
|
const originalHandleClick = proto.handleClick;
|
||||||
proto.handleClick = function (x, y) {
|
proto.handleClick = function (x, y) {
|
||||||
if (tryPendingThawAt(x, y, this)) return;
|
if (tryPendingThawAt(x, y, this)) return;
|
||||||
if (this.tool === "freeze") {
|
|
||||||
global.showToast?.("冷凍は『つまむ』でたりないを冷凍個体UIへ運んでください。");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return originalHandleClick.call(this, x, y);
|
return originalHandleClick.call(this, x, y);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
561
js/items.js
561
js/items.js
|
|
@ -174,6 +174,237 @@ function drawBulletSprite(ctx, r) {
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isConsumableSpriteType(type = "") {
|
||||||
|
const key = String(type || "");
|
||||||
|
return ["food", "sweet", "love_mochi", "fight_mochi", "sleep_drug"].includes(key)
|
||||||
|
|| (typeof isParamEffectItemType === "function" && isParamEffectItemType(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawConsumableFieldSprite(ctx, type = "", r = 10, seed = 1, opts = {}) {
|
||||||
|
const key = String(type || "food");
|
||||||
|
const localSeed = Number(seed || 1) || 1;
|
||||||
|
const localR = Math.max(1, Number(r) || 10);
|
||||||
|
const compact = !!opts.compact;
|
||||||
|
ctx.save();
|
||||||
|
if (key === "food" || key === "sweet" || key === "love_mochi" || key === "fight_mochi") {
|
||||||
|
const sweet = key === "sweet";
|
||||||
|
const loveMochi = key === "love_mochi";
|
||||||
|
const fightMochi = key === "fight_mochi";
|
||||||
|
ctx.fillStyle = loveMochi ? "#fff1f6" : (fightMochi ? "#fff3ec" : (sweet ? "#fffaf1" : "#f1dfb7"));
|
||||||
|
ctx.strokeStyle = loveMochi ? "#bf6f8b" : (fightMochi ? "#bc6d42" : (sweet ? "#9a8a72" : "#8d6f48"));
|
||||||
|
ctx.lineWidth = Math.max(1.2, localR * 0.15);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(0, 0, localR * 1.25, localR * 0.75, Math.sin(localSeed) * 0.4, 0, Math.PI * 2);
|
||||||
|
ctx.fill(); ctx.stroke();
|
||||||
|
if (sweet) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = "#76b94d";
|
||||||
|
ctx.ellipse(-localR * 0.07, -localR * 0.24, localR * 0.76, localR * 0.26, Math.sin(localSeed) * 0.25, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.62)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-localR * 0.32, -localR * 0.16, localR * 0.24, localR * 0.10, -0.35, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
} else if (loveMochi) {
|
||||||
|
ctx.fillStyle = "#ff6a9c";
|
||||||
|
ctx.font = `${Math.round(localR * 1.15)}px sans-serif`;
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
ctx.fillText("❤", 0, 1);
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.55)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-localR * 0.28, -localR * 0.18, localR * 0.23, localR * 0.10, -0.35, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
} else if (fightMochi) {
|
||||||
|
ctx.strokeStyle = "rgba(168,76,42,0.82)";
|
||||||
|
ctx.lineWidth = Math.max(1.2, localR * 0.13);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(-localR * 0.44, -localR * 0.42);
|
||||||
|
ctx.lineTo(localR * 0.44, localR * 0.42);
|
||||||
|
ctx.moveTo(-localR * 0.44, localR * 0.42);
|
||||||
|
ctx.lineTo(localR * 0.44, -localR * 0.42);
|
||||||
|
ctx.stroke();
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = "#fff7dc";
|
||||||
|
const dots = compact ? 4 : 5;
|
||||||
|
for (let i = 0; i < dots; i++) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(randSeed(localSeed + i, -localR * 0.55, localR * 0.55), randSeed(localSeed + i + 7, -localR * 0.34, localR * 0.34), Math.max(1.0, localR * 0.12), 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (key === "grass") {
|
||||||
|
ctx.strokeStyle = "#5fa53f";
|
||||||
|
ctx.lineWidth = Math.max(1.3, localR * 0.14);
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const a = -Math.PI / 2 + randSeed(localSeed + i, -0.75, 0.75);
|
||||||
|
const len = localR * randSeed(localSeed + i + 10, 0.65, 1.15);
|
||||||
|
const rootX = randSeed(localSeed + i + 20, -localR * 0.32, localR * 0.32);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(rootX, localR * 0.35);
|
||||||
|
ctx.quadraticCurveTo(rootX + Math.cos(a) * len * 0.46, -localR * 0.05, rootX + Math.cos(a) * len, Math.sin(a) * len * 0.72);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
} else if (key === "sleep_drug") {
|
||||||
|
drawSleepTabletSprite(ctx, localR * visualFieldScaleFor("sleep_drug"), visualPaletteFor("sleep_drug"));
|
||||||
|
} else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(key)) {
|
||||||
|
const visual = itemVisualFor(key);
|
||||||
|
const palette = visual?.palette || {};
|
||||||
|
const renderer = visual?.renderer || "split_pill";
|
||||||
|
const fill = palette.fill || "#fffaf1";
|
||||||
|
const stroke = palette.stroke || "#8d6f48";
|
||||||
|
const accent = palette.highlight || palette.accent || "#ddd";
|
||||||
|
const rr = localR * (visual?.fieldScale || visual?.scale || 1);
|
||||||
|
if (renderer === "powder_pile") {
|
||||||
|
ctx.fillStyle = "rgba(68,46,28,0.18)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(0, rr * 0.50, rr * 1.22, rr * 0.34, 0, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
const count = compact ? 10 : 18;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const px = randSeed(localSeed + i * 7, -rr * 0.85, rr * 0.85);
|
||||||
|
const layer = 1 - Math.abs(px) / Math.max(1, rr * 0.90);
|
||||||
|
const py = randSeed(localSeed + i * 11, -rr * 0.15, rr * 0.48) - layer * rr * 0.48;
|
||||||
|
const blobR = randSeed(localSeed + i * 13, rr * 0.14, rr * 0.32) * (0.75 + layer * 0.45);
|
||||||
|
ctx.fillStyle = i % 3 === 0 ? accent : fill;
|
||||||
|
ctx.strokeStyle = stroke;
|
||||||
|
ctx.globalAlpha = 0.86 + layer * 0.12;
|
||||||
|
ctx.lineWidth = Math.max(0.8, rr * 0.055);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(px, py, blobR * 1.18, blobR * 0.72, randSeed(localSeed + i, -0.45, 0.45), 0, Math.PI * 2);
|
||||||
|
ctx.fill(); ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.62)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-rr * 0.30, -rr * 0.42, rr * 0.28, rr * 0.10, -0.35, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
} else if (renderer === "oval_droplet") {
|
||||||
|
drawOvalWaterDropSprite(ctx, rr, { fill, stroke, highlight: accent });
|
||||||
|
} else if (renderer === "laxative_tablet") {
|
||||||
|
drawLaxativeTabletSprite(ctx, rr);
|
||||||
|
} else if (renderer === "mystery_tablet") {
|
||||||
|
drawMysteryTabletSprite(ctx, rr);
|
||||||
|
} else if (renderer === "bullet") {
|
||||||
|
drawBulletSprite(ctx, rr);
|
||||||
|
} else {
|
||||||
|
ctx.save();
|
||||||
|
ctx.rotate(-0.38);
|
||||||
|
ctx.fillStyle = fill;
|
||||||
|
ctx.strokeStyle = stroke;
|
||||||
|
ctx.lineWidth = Math.max(1.3, rr * 0.12);
|
||||||
|
roundedRect(ctx, -rr * 1.15, -rr * 0.46, rr * 2.30, rr * 0.92, rr * 0.46);
|
||||||
|
ctx.fill(); ctx.stroke();
|
||||||
|
ctx.fillStyle = accent;
|
||||||
|
ctx.globalAlpha = 0.78;
|
||||||
|
ctx.fillRect(-rr * 0.10, -rr * 0.42, rr * 0.20, rr * 0.84);
|
||||||
|
ctx.globalAlpha = 0.36;
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.92)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-rr * 0.48, -rr * 0.16, rr * 0.32, rr * 0.10, -0.10, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = "#e4ba5b";
|
||||||
|
ctx.strokeStyle = "rgba(105,74,39,0.62)";
|
||||||
|
ctx.lineWidth = Math.max(1.1, localR * 0.11);
|
||||||
|
ctx.beginPath(); ctx.ellipse(0, 0, localR * 0.98, localR * 0.58, -0.08, 0, Math.PI * 2); ctx.fill(); ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawStoredConsumableSprite(ctx, type = "", r = 10, seed = 1) {
|
||||||
|
drawConsumableFieldSprite(ctx, type || "food", r, seed, { compact: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewItemRadiusFor(type = "") {
|
||||||
|
const def = typeof itemDefinition === "function" ? itemDefinition(type) : null;
|
||||||
|
const base = Number(def?.radius || itemRadiusFor?.(type, 16) || 16);
|
||||||
|
if (type === "genkotsu") return 30;
|
||||||
|
if (type === "fence_v" || type === "fence_h") return 24;
|
||||||
|
if (type === "nest_box") return 26;
|
||||||
|
if (type === "duplicator") return 24;
|
||||||
|
if (type === "signboard") return 22;
|
||||||
|
if (type === "bed") return 22;
|
||||||
|
if (type === "pushpin" || type === "oshibyo") return 12;
|
||||||
|
return clamp(base, 8, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawToolItemPreview(ctx, type = "", opts = {}) {
|
||||||
|
if (!ctx || !type) return false;
|
||||||
|
const w = Math.max(1, Number(opts.width || ctx.canvas?.width || 72));
|
||||||
|
const h = Math.max(1, Number(opts.height || ctx.canvas?.height || 72));
|
||||||
|
const localR = previewItemRadiusFor(type);
|
||||||
|
const item = Object.create(Item.prototype);
|
||||||
|
Object.assign(item, {
|
||||||
|
id: `tool-preview-${type}`,
|
||||||
|
type,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
r: localR,
|
||||||
|
amount: 999,
|
||||||
|
age: 0,
|
||||||
|
seed: 72.43,
|
||||||
|
roles: {},
|
||||||
|
dropTimer: 0,
|
||||||
|
dropMax: 0,
|
||||||
|
dropImpactDone: false,
|
||||||
|
growth: type === "grass" ? 0.86 : undefined,
|
||||||
|
health: 1,
|
||||||
|
wear: 0,
|
||||||
|
text: type === "signboard" ? "" : undefined,
|
||||||
|
storedFoodType: type === "duplicator" ? "sweet" : "",
|
||||||
|
storedFoodLabel: type === "duplicator" ? "ずんだ餅" : "",
|
||||||
|
pinState: "loose",
|
||||||
|
spin: type === "pushpin" || type === "oshibyo" ? -0.55 : 0,
|
||||||
|
zunchiVariant: "zunchi",
|
||||||
|
antCount: typeof ANT_NEST_START_COUNT !== "undefined" ? ANT_NEST_START_COUNT : 6,
|
||||||
|
fuseTimer: 5,
|
||||||
|
fuseMax: 5,
|
||||||
|
impactFlash: 0,
|
||||||
|
foodServingsMax: typeof foodServingsForSize === "function" ? foodServingsForSize("medium") : 5,
|
||||||
|
foodServingsRemaining: typeof foodServingsForSize === "function" ? foodServingsForSize("medium") : 5,
|
||||||
|
toolSize: "medium",
|
||||||
|
});
|
||||||
|
if (type === "water") item.amount = 50;
|
||||||
|
ctx.save();
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
ctx.translate(w / 2, h / 2 + (type === "signboard" ? 11 : type === "genkotsu" ? 12 : type === "fence_v" || type === "fence_h" ? 1 : 4));
|
||||||
|
const scaleMap = {
|
||||||
|
genkotsu: 0.52,
|
||||||
|
bed: 0.80,
|
||||||
|
nest_box: 0.78,
|
||||||
|
signboard: 0.76,
|
||||||
|
duplicator: 0.82,
|
||||||
|
fence_v: 0.70,
|
||||||
|
fence_h: 0.70,
|
||||||
|
ant_nest: 0.92,
|
||||||
|
ball: 0.92,
|
||||||
|
pushpin: 0.92,
|
||||||
|
oshibyo: 0.92,
|
||||||
|
firecracker: 0.90,
|
||||||
|
};
|
||||||
|
ctx.scale(scaleMap[type] || 0.92, scaleMap[type] || 0.92);
|
||||||
|
try {
|
||||||
|
item.draw(ctx, 0, typeof getLightingState === "function" ? getLightingState(globalThis.world) : null);
|
||||||
|
} catch (err) {
|
||||||
|
ctx.restore();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.isConsumableSpriteType = isConsumableSpriteType;
|
||||||
|
window.drawConsumableFieldSprite = drawConsumableFieldSprite;
|
||||||
|
window.drawStoredConsumableSprite = drawStoredConsumableSprite;
|
||||||
|
window.drawToolItemPreview = drawToolItemPreview;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Item {
|
class Item {
|
||||||
constructor(type, x, y) {
|
constructor(type, x, y) {
|
||||||
this.id = crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
|
this.id = crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`;
|
||||||
|
|
@ -190,7 +421,7 @@ class Item {
|
||||||
if (type === "water" || type === "water_bowl" || type === "zunda_juice") this.roles.drink = true;
|
if (type === "water" || type === "water_bowl" || type === "zunda_juice") this.roles.drink = true;
|
||||||
if (["sweet", "water", "water_bowl", "zunda_juice"].includes(type) || isParamEffectItemType(type)) this.roles.medicine = true;
|
if (["sweet", "water", "water_bowl", "zunda_juice"].includes(type) || isParamEffectItemType(type)) this.roles.medicine = true;
|
||||||
if (type === "bed" || type === "nest_box") this.roles.sleepPlace = true;
|
if (type === "bed" || type === "nest_box") this.roles.sleepPlace = true;
|
||||||
if (["firecracker", "genkotsu", "pushpin", "oshibyo", "zunchi", "splat"].includes(type)) this.roles.danger = true;
|
if (["firecracker", "genkotsu", "pushpin", "zunchi", "splat"].includes(type)) this.roles.danger = true;
|
||||||
if (type === "grass") this.roles.grassMaterial = true;
|
if (type === "grass") this.roles.grassMaterial = true;
|
||||||
this.dropTimer = 0;
|
this.dropTimer = 0;
|
||||||
this.dropMax = 0;
|
this.dropMax = 0;
|
||||||
|
|
@ -214,6 +445,14 @@ class Item {
|
||||||
this.text = "";
|
this.text = "";
|
||||||
this.textEditedAt = -999;
|
this.textEditedAt = -999;
|
||||||
}
|
}
|
||||||
|
if (type === "duplicator") {
|
||||||
|
this.storedFoodType = "";
|
||||||
|
this.storedFoodLabel = "";
|
||||||
|
this.roles.food = false;
|
||||||
|
this.roles.duplicator = true;
|
||||||
|
this.needEffects.food = 55;
|
||||||
|
this.amount = 999;
|
||||||
|
}
|
||||||
if (type === "ball") {
|
if (type === "ball") {
|
||||||
this.vx = rand(-5, 5);
|
this.vx = rand(-5, 5);
|
||||||
this.vy = rand(-5, 5);
|
this.vy = rand(-5, 5);
|
||||||
|
|
@ -350,6 +589,7 @@ class Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.type === "ball") this.updateBall(dt, worldRef);
|
if (this.type === "ball") this.updateBall(dt, worldRef);
|
||||||
|
if (this.type === "duplicator") this.updateDuplicator(dt, worldRef);
|
||||||
if (isPinType(this.type)) this.updatePushpin(dt, worldRef);
|
if (isPinType(this.type)) this.updatePushpin(dt, worldRef);
|
||||||
if (this.type === "zunchi") this.updateZunchiMotion(dt, worldRef);
|
if (this.type === "zunchi") this.updateZunchiMotion(dt, worldRef);
|
||||||
if (this.type === "grass") {
|
if (this.type === "grass") {
|
||||||
|
|
@ -372,6 +612,38 @@ class Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateDuplicator(dt, worldRef) {
|
||||||
|
this.amount = 999;
|
||||||
|
this.roles.food = Boolean(this.storedFoodType);
|
||||||
|
if (!worldRef?.nearbyItems) return;
|
||||||
|
const candidates = worldRef.nearbyItems(this.x, this.y, Math.max(74, (this.r || 34) * 2.4));
|
||||||
|
let best = null, bestD = Infinity;
|
||||||
|
for (const it of candidates) {
|
||||||
|
if (!it || it === this || it.dead) continue;
|
||||||
|
if (typeof isServingFoodType !== "function" || !isServingFoodType(it.type)) continue;
|
||||||
|
if ((it.foodServingsRemaining ?? it.amount ?? 0) <= 0) continue;
|
||||||
|
const d = distXY(this.x, this.y, it.x, it.y);
|
||||||
|
if (d < bestD && d <= Math.max(58, (this.r || 34) + (it.r || 14) + 18)) { best = it; bestD = d; }
|
||||||
|
}
|
||||||
|
if (!best) return;
|
||||||
|
const newType = best.type;
|
||||||
|
const def = typeof itemDefinition === "function" ? itemDefinition(newType) : null;
|
||||||
|
const newLabel = def?.label || newType;
|
||||||
|
const changed = this.storedFoodType !== newType;
|
||||||
|
if (!changed && this.storedFoodType) return;
|
||||||
|
this.storedFoodType = newType;
|
||||||
|
this.storedFoodLabel = newLabel;
|
||||||
|
this.roles.food = true;
|
||||||
|
this.loadedAt = worldRef.time || 0;
|
||||||
|
best.amount = 0;
|
||||||
|
best.foodServingsRemaining = 0;
|
||||||
|
// Item.dead is a getter based on amount. Do not assign to it.
|
||||||
|
worldRef.markSpatialDirty?.(changed ? "duplicator-reloaded" : "duplicator-loaded");
|
||||||
|
worldRef.markTerrainDirty?.(changed ? "duplicator-reloaded" : "duplicator-loaded");
|
||||||
|
worldRef.emit?.("duplicator:loaded", { duplicator: this, foodType: this.storedFoodType, replaced: changed });
|
||||||
|
worldRef.log?.(`\u8907\u88fd\u6a5f\u306b${this.storedFoodLabel}\u3092\u30bb\u30c3\u30c8\u3057\u305f\u3002`, "food");
|
||||||
|
}
|
||||||
updateZunchiMotion(dt, worldRef) {
|
updateZunchiMotion(dt, worldRef) {
|
||||||
const speed = Math.hypot(this.vx || 0, this.vy || 0);
|
const speed = Math.hypot(this.vx || 0, this.vy || 0);
|
||||||
if (speed < 0.08) { this.vx = 0; this.vy = 0; return; }
|
if (speed < 0.08) { this.vx = 0; this.vy = 0; return; }
|
||||||
|
|
@ -676,9 +948,9 @@ class Item {
|
||||||
this.pinState = "lodged";
|
this.pinState = "lodged";
|
||||||
this.deletable = false;
|
this.deletable = false;
|
||||||
this.pinTargetId = t.id;
|
this.pinTargetId = t.id;
|
||||||
this.pinAttachAngle = isOshibyo ? 0.62 : Math.atan2(dy || -1, dx || (t.facingDir ? t.facingDir() : 1));
|
this.pinAttachAngle = isOshibyo ? 0.36 : Math.atan2(dy || -1, dx || (t.facingDir ? t.facingDir() : 1));
|
||||||
this.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.58 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
|
this.pinAttachDistance = isOshibyo ? (t.radius || 16) * 0.72 : clamp(distToTarget || (t.radius || 16) * 0.58, (t.radius || 16) * 0.20, (t.radius || 16) * 0.74);
|
||||||
this.pinOffsetY = isOshibyo ? (t.radius || 16) * 0.30 : clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
|
this.pinOffsetY = isOshibyo ? (t.radius || 16) * 0.40 : clamp(dy, -(t.radius || 16) * 0.74, (t.radius || 16) * 0.38);
|
||||||
this.pinDamageTick = 0;
|
this.pinDamageTick = 0;
|
||||||
this.pinFallCheckTimer = 0;
|
this.pinFallCheckTimer = 0;
|
||||||
this.vx = 0;
|
this.vx = 0;
|
||||||
|
|
@ -722,8 +994,8 @@ class Item {
|
||||||
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
const isOshibyo = Boolean(behavior?.blocksZunchi);
|
||||||
t.stuckPushpinId = this.id;
|
t.stuckPushpinId = this.id;
|
||||||
this.deletable = false;
|
this.deletable = false;
|
||||||
this.x = isOshibyo ? t.x + (t.radius || 16) * 0.42 : t.x + (t.radius || 16) * 0.36;
|
this.x = isOshibyo ? t.x + (t.radius || 16) * 0.66 : t.x + (t.radius || 16) * 0.36;
|
||||||
this.y = isOshibyo ? t.y + (t.radius || 16) * 0.34 : t.y - (t.radius || 16) * 0.04;
|
this.y = isOshibyo ? t.y + (t.radius || 16) * 0.42 : t.y - (t.radius || 16) * 0.04;
|
||||||
this.prevX = this.x;
|
this.prevX = this.x;
|
||||||
this.prevY = this.y;
|
this.prevY = this.y;
|
||||||
this.spin = isOshibyo ? 0.52 : 0.28;
|
this.spin = isOshibyo ? 0.52 : 0.28;
|
||||||
|
|
@ -957,135 +1229,8 @@ class Item {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(this.x, this.y + dropY);
|
ctx.translate(this.x, this.y + dropY);
|
||||||
ctx.globalAlpha = visibleAlpha;
|
ctx.globalAlpha = visibleAlpha;
|
||||||
if (this.type === "food" || this.type === "sweet" || this.type === "love_mochi" || this.type === "fight_mochi") {
|
if (isConsumableSpriteType(this.type)) {
|
||||||
const sweet = this.type === "sweet";
|
drawConsumableFieldSprite(ctx, this.type, this.r, this.seed);
|
||||||
const loveMochi = this.type === "love_mochi";
|
|
||||||
const fightMochi = this.type === "fight_mochi";
|
|
||||||
ctx.fillStyle = loveMochi ? "#fff1f6" : (fightMochi ? "#fff3ec" : (sweet ? "#fffaf1" : "#f1dfb7"));
|
|
||||||
ctx.strokeStyle = loveMochi ? "#bf6f8b" : (fightMochi ? "#bc6d42" : (sweet ? "#9a8a72" : "#8d6f48"));
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.beginPath();
|
|
||||||
const bob = 0;
|
|
||||||
ctx.ellipse(0, bob, this.r * 1.25, this.r * 0.75, Math.sin(this.seed) * 0.4, 0, Math.PI * 2);
|
|
||||||
ctx.fill(); ctx.stroke();
|
|
||||||
if (sweet) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.fillStyle = "#76b94d";
|
|
||||||
ctx.ellipse(-1, -this.r * 0.24, this.r * 0.76, this.r * 0.26, Math.sin(this.seed) * 0.25, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.fillStyle = "rgba(255,255,255,0.62)";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(-this.r * 0.32, -this.r * 0.16, this.r * 0.24, this.r * 0.10, -0.35, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
} else if (loveMochi) {
|
|
||||||
ctx.fillStyle = "#ff6a9c";
|
|
||||||
ctx.font = `${Math.round((this.r || 13) * 1.15)}px sans-serif`;
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.fillText("\u2764", 0, 1);
|
|
||||||
ctx.fillStyle = "rgba(255,255,255,0.55)";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(-this.r * 0.28, -this.r * 0.18, this.r * 0.23, this.r * 0.10, -0.35, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
} else if (fightMochi) {
|
|
||||||
ctx.strokeStyle = "rgba(168,76,42,0.82)";
|
|
||||||
ctx.lineWidth = 1.8;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(-this.r * 0.44, -this.r * 0.42);
|
|
||||||
ctx.lineTo(this.r * 0.44, this.r * 0.42);
|
|
||||||
ctx.moveTo(-this.r * 0.44, this.r * 0.42);
|
|
||||||
ctx.lineTo(this.r * 0.44, -this.r * 0.42);
|
|
||||||
ctx.stroke();
|
|
||||||
} else {
|
|
||||||
ctx.fillStyle = "#fff7dc";
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(randSeed(this.seed + i, -8, 8), randSeed(this.seed + i + 7, -5, 5), 1.6, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(this.type)) {
|
|
||||||
const visual = itemVisualFor(this.type);
|
|
||||||
const palette = visual?.palette || {};
|
|
||||||
const renderer = visual?.renderer || "split_pill";
|
|
||||||
const meta = {
|
|
||||||
fill: palette.fill || "#fffaf1",
|
|
||||||
stroke: palette.stroke || "#8d6f48",
|
|
||||||
accent: palette.highlight || palette.accent || "#ddd",
|
|
||||||
kind: renderer === "powder_pile" ? "powder" : renderer === "oval_droplet" ? "droplet" : renderer === "laxative_tablet" ? "laxative" : renderer === "mystery_tablet" ? "mystery" : renderer === "bullet" ? "bullet" : "pill",
|
|
||||||
};
|
|
||||||
const localR = this.r * (visual?.fieldScale || visual?.scale || 1);
|
|
||||||
ctx.rotate(Math.sin(this.seed) * 0.10);
|
|
||||||
if (meta.kind === "powder") {
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = "rgba(68,46,28,0.18)";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(0, localR * 0.50, localR * 1.22, localR * 0.34, 0, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
for (let i = 0; i < 18; i++) {
|
|
||||||
const px = randSeed(this.seed + i * 7, -localR * 0.85, localR * 0.85);
|
|
||||||
const layer = 1 - Math.abs(px) / Math.max(1, localR * 0.90);
|
|
||||||
const py = randSeed(this.seed + i * 11, -localR * 0.15, localR * 0.48) - layer * localR * 0.48;
|
|
||||||
const rr = randSeed(this.seed + i * 13, localR * 0.14, localR * 0.32) * (0.75 + layer * 0.45);
|
|
||||||
ctx.fillStyle = i % 3 === 0 ? meta.accent : meta.fill;
|
|
||||||
ctx.strokeStyle = meta.stroke;
|
|
||||||
ctx.globalAlpha = 0.86 + layer * 0.12;
|
|
||||||
ctx.lineWidth = 1.0;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(px, py, rr * 1.18, rr * 0.72, randSeed(this.seed + i, -0.45, 0.45), 0, Math.PI * 2);
|
|
||||||
ctx.fill(); ctx.stroke();
|
|
||||||
}
|
|
||||||
ctx.globalAlpha = 1;
|
|
||||||
ctx.fillStyle = "rgba(255,255,255,0.62)";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(-localR * 0.30, -localR * 0.42, localR * 0.28, localR * 0.10, -0.35, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.restore();
|
|
||||||
} else if (meta.kind === "droplet") {
|
|
||||||
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;
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
roundedRect(ctx, -localR * 0.96, -localR * 0.78, localR * 1.92, localR * 1.56, localR * 0.24);
|
|
||||||
ctx.fill(); ctx.stroke();
|
|
||||||
ctx.fillStyle = meta.accent;
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(randSeed(this.seed + i, -localR * 0.50, localR * 0.50), randSeed(this.seed + i + 5, -localR * 0.30, localR * 0.34), localR * 0.18, localR * 0.34, 0.28, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
ctx.strokeStyle = "rgba(110,76,30,0.52)";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(-localR * 0.70, localR * 0.50);
|
|
||||||
ctx.lineTo(localR * 0.70, -localR * 0.50);
|
|
||||||
ctx.stroke();
|
|
||||||
} else {
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = meta.fill;
|
|
||||||
ctx.strokeStyle = meta.stroke;
|
|
||||||
ctx.lineWidth = 2.2;
|
|
||||||
ctx.rotate(-0.38);
|
|
||||||
roundedRect(ctx, -localR * 1.15, -localR * 0.46, localR * 2.30, localR * 0.92, localR * 0.46);
|
|
||||||
ctx.fill(); ctx.stroke();
|
|
||||||
ctx.fillStyle = meta.accent;
|
|
||||||
ctx.globalAlpha = 0.78;
|
|
||||||
ctx.fillRect(-1.5, -localR * 0.42, 3.0, localR * 0.84);
|
|
||||||
ctx.globalAlpha = 0.36;
|
|
||||||
ctx.fillStyle = "rgba(255,255,255,0.92)";
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(-localR * 0.48, -localR * 0.16, localR * 0.32, localR * 0.10, -0.10, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
||||||
} else if (this.type === "sleep_drug") {
|
|
||||||
drawSleepTabletSprite(ctx, this.r * visualFieldScaleFor("sleep_drug"), visualPaletteFor("sleep_drug"));
|
|
||||||
} else if (this.type === "genkotsu") {
|
} else if (this.type === "genkotsu") {
|
||||||
const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu");
|
const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu");
|
||||||
const impact = clamp(this.impactFlash || 0, 0, 1);
|
const impact = clamp(this.impactFlash || 0, 0, 1);
|
||||||
|
|
@ -1124,6 +1269,7 @@ class Item {
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
} else if (isPinType(this.type)) {
|
} else if (isPinType(this.type)) {
|
||||||
const stuck = this.pinState === "lodged";
|
const stuck = this.pinState === "lodged";
|
||||||
|
const isOshibyo = this.type === "oshibyo";
|
||||||
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null;
|
||||||
const assetId = behavior ? (stuck ? behavior.lodgedAsset : behavior.looseAsset) : (stuck ? "pushpin_stuck" : "pushpin");
|
const assetId = behavior ? (stuck ? behavior.lodgedAsset : behavior.looseAsset) : (stuck ? "pushpin_stuck" : "pushpin");
|
||||||
const img = typeof getRenderableImage === "function" ? getRenderableImage(assetId, assetId) : images.get(assetId);
|
const img = typeof getRenderableImage === "function" ? getRenderableImage(assetId, assetId) : images.get(assetId);
|
||||||
|
|
@ -1135,7 +1281,7 @@ class Item {
|
||||||
if (img) {
|
if (img) {
|
||||||
const metrics = typeof getImageMetrics === "function" ? getImageMetrics(assetId) : null;
|
const metrics = typeof getImageMetrics === "function" ? getImageMetrics(assetId) : null;
|
||||||
const ratio = metrics?.ratio || (stuck ? 1 : 2.0);
|
const ratio = metrics?.ratio || (stuck ? 1 : 2.0);
|
||||||
const w = isOshibyo ? (stuck ? this.r * 3.1 : this.r * 1.55) : (stuck ? this.r * 2.15 : this.r * 1.78);
|
const w = isOshibyo ? (stuck ? this.r * 1.55 : this.r * 0.78) : (stuck ? this.r * 2.15 : this.r * 1.78);
|
||||||
const h = w * ratio;
|
const h = w * ratio;
|
||||||
ctx.drawImage(img, -w * 0.5, -h * 0.5, w, h);
|
ctx.drawImage(img, -w * 0.5, -h * 0.5, w, h);
|
||||||
} else if (stuck) {
|
} else if (stuck) {
|
||||||
|
|
@ -1268,31 +1414,136 @@ class Item {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(0, -this.r * 0.08, this.r * 0.40, 0, Math.PI * 2);
|
ctx.arc(0, -this.r * 0.08, this.r * 0.40, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
} else if (this.type === "signboard") {
|
} else if (this.type === "duplicator") {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.fillStyle = styleNight ? "#6d543b" : (styleWarm ? "#a67848" : "#8d633e");
|
const loaded = !!this.storedFoodType;
|
||||||
ctx.strokeStyle = styleNight ? "#34281c" : "#573c27";
|
const bodyGrad = ctx.createLinearGradient(-this.r * 1.1, -this.r * 1.0, this.r * 1.1, this.r * 0.9);
|
||||||
ctx.lineWidth = 2.2;
|
bodyGrad.addColorStop(0, styleNight ? "#3d4852" : "#d8e2e6");
|
||||||
ctx.fillRect(-this.r * 0.14, -this.r * 0.05, this.r * 0.28, this.r * 1.58);
|
bodyGrad.addColorStop(0.45, styleNight ? "#65717b" : "#f2f6f7");
|
||||||
ctx.strokeRect(-this.r * 0.14, -this.r * 0.05, this.r * 0.28, this.r * 1.58);
|
bodyGrad.addColorStop(1, styleNight ? "#2c343c" : "#9fafb7");
|
||||||
roundedRect(ctx, -this.r * 1.18, -this.r * 1.18, this.r * 2.36, this.r * 1.18, this.r * 0.14);
|
ctx.fillStyle = bodyGrad;
|
||||||
|
ctx.strokeStyle = styleNight ? "#1b252c" : "#52636b";
|
||||||
|
ctx.lineWidth = Math.max(2, this.r * 0.075);
|
||||||
|
roundedRect(ctx, -this.r * 1.10, -this.r * 0.86, this.r * 1.52, this.r * 1.52, this.r * 0.20);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.fillStyle = "rgba(255,255,255,0.16)";
|
|
||||||
ctx.fillRect(-this.r * 0.92, -this.r * 0.96, this.r * 1.02, this.r * 0.14);
|
ctx.fillStyle = styleNight ? "rgba(20,28,34,0.52)" : "rgba(255,255,255,0.72)";
|
||||||
const raw = String(this.text || "").replace(/\s+/g, " ").trim();
|
roundedRect(ctx, -this.r * 0.92, -this.r * 0.64, this.r * 1.12, this.r * 0.42, this.r * 0.10);
|
||||||
if (raw) {
|
ctx.fill();
|
||||||
const charsPerLine = 7;
|
ctx.strokeStyle = styleNight ? "rgba(160,190,210,0.25)" : "rgba(82,98,108,0.32)";
|
||||||
const lines = [];
|
ctx.stroke();
|
||||||
for (let i = 0; i < raw.length && lines.length < 2; i += charsPerLine) lines.push(raw.slice(i, i + charsPerLine));
|
|
||||||
ctx.fillStyle = styleNight ? "rgba(255,249,236,0.92)" : "rgba(51,34,19,0.92)";
|
ctx.fillStyle = loaded ? "rgba(83,221,116,0.88)" : "rgba(139,160,170,0.62)";
|
||||||
ctx.font = `bold ${Math.max(8, Math.round(this.r * 0.38))}px ui-rounded, sans-serif`;
|
ctx.beginPath();
|
||||||
|
ctx.arc(-this.r * 0.70, -this.r * 0.43, this.r * 0.13, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.strokeStyle = "rgba(35,47,54,0.52)";
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = loaded ? "rgba(126,245,141,0.82)" : "rgba(255,255,255,0.45)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-this.r * 0.74, -this.r * 0.47, this.r * 0.045, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.strokeStyle = styleNight ? "#232b30" : "#657079";
|
||||||
|
ctx.lineWidth = Math.max(1.6, this.r * 0.045);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(this.r * 0.18, -this.r * 0.34);
|
||||||
|
ctx.quadraticCurveTo(this.r * 0.48, -this.r * 0.52, this.r * 0.74, -this.r * 0.28);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = styleNight ? "#283139" : "#e6edf0";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(this.r * 0.78, -this.r * 0.28, this.r * 0.10, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
const dishGrad = ctx.createLinearGradient(this.r * 0.08, -this.r * 0.06, this.r * 1.28, this.r * 0.62);
|
||||||
|
dishGrad.addColorStop(0, styleNight ? "#3f454b" : "#eef4f4");
|
||||||
|
dishGrad.addColorStop(0.58, styleNight ? "#737c82" : "#ffffff");
|
||||||
|
dishGrad.addColorStop(1, styleNight ? "#242a30" : "#adb8bd");
|
||||||
|
ctx.fillStyle = dishGrad;
|
||||||
|
ctx.strokeStyle = styleNight ? "#12181d" : "#67767e";
|
||||||
|
ctx.lineWidth = Math.max(2, this.r * 0.06);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(this.r * 0.62, this.r * 0.42, this.r * 0.83, this.r * 0.39, -0.08, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = loaded ? "rgba(255,245,209,0.96)" : "rgba(230,235,238,0.48)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(this.r * 0.62, this.r * 0.32, this.r * 0.50, this.r * 0.23, -0.08, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
if (loaded) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(this.r * 0.62, this.r * 0.24);
|
||||||
|
ctx.scale(0.82, 0.82);
|
||||||
|
drawStoredConsumableSprite(ctx, this.storedFoodType || "food", Math.max(7, this.r * 0.35), this.seed + 241);
|
||||||
|
ctx.restore();
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = "rgba(72,80,86,0.62)";
|
||||||
|
ctx.font = `bold ${Math.max(10, Math.round(this.r * 0.34))}px ui-rounded, sans-serif`;
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
lines.forEach((line, index) => ctx.fillText(line, 0, -this.r * 0.68 + index * this.r * 0.34));
|
ctx.fillText("?", this.r * 0.62, this.r * 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = styleNight ? "rgba(255,255,255,0.12)" : "rgba(255,255,255,0.45)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-this.r * 0.48, -this.r * 0.72, this.r * 0.34, this.r * 0.08, -0.14, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
} else if (this.type === "signboard") {
|
||||||
|
ctx.save();
|
||||||
|
const boardGrad = ctx.createLinearGradient(0, -this.r * 1.28, 0, this.r * 0.12);
|
||||||
|
boardGrad.addColorStop(0, styleNight ? "#d5cab8" : "#fff8e8");
|
||||||
|
boardGrad.addColorStop(0.55, styleNight ? "#c0b29a" : "#f3e3c6");
|
||||||
|
boardGrad.addColorStop(1, styleNight ? "#aa9a82" : "#dfc397");
|
||||||
|
ctx.fillStyle = styleNight ? "#8a7557" : "#d6ba87";
|
||||||
|
ctx.strokeStyle = styleNight ? "#5d4d3b" : "#9b7444";
|
||||||
|
ctx.lineWidth = 2.2;
|
||||||
|
roundedRect(ctx, -this.r * 0.13, -this.r * 0.02, this.r * 0.26, this.r * 1.78, this.r * 0.04);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = boardGrad;
|
||||||
|
ctx.strokeStyle = styleNight ? "#63513c" : "#a77e48";
|
||||||
|
roundedRect(ctx, -this.r * 1.36, -this.r * 1.28, this.r * 2.72, this.r * 1.38, this.r * 0.18);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.strokeStyle = styleNight ? "rgba(103,83,58,0.36)" : "rgba(167,126,72,0.38)";
|
||||||
|
ctx.lineWidth = 1.1;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const y = -this.r * 1.02 + i * this.r * 0.36;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(-this.r * 1.12, y);
|
||||||
|
ctx.quadraticCurveTo(-this.r * 0.12, y + Math.sin(this.seed + i) * 2, this.r * 1.12, y + Math.cos(this.seed + i) * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.fillStyle = "rgba(255,255,255,0.28)";
|
||||||
|
roundedRect(ctx, -this.r * 1.12, -this.r * 1.10, this.r * 2.24, this.r * 0.20, this.r * 0.08);
|
||||||
|
ctx.fill();
|
||||||
|
const raw = String(this.text || "").replace(/\r/g, "").trim();
|
||||||
|
if (raw) {
|
||||||
|
const charsPerLine = 6;
|
||||||
|
const lines = [];
|
||||||
|
for (const rawLine of raw.split("\n")) {
|
||||||
|
let rest = rawLine.trim();
|
||||||
|
if (!rest && lines.length < 3) { lines.push(""); continue; }
|
||||||
|
while (rest && lines.length < 3) {
|
||||||
|
const chars = Array.from(rest);
|
||||||
|
lines.push(chars.slice(0, charsPerLine).join(""));
|
||||||
|
rest = chars.slice(charsPerLine).join("");
|
||||||
|
}
|
||||||
|
if (lines.length >= 3) break;
|
||||||
|
}
|
||||||
|
ctx.fillStyle = styleNight ? "rgba(37,31,24,0.95)" : "rgba(55,37,22,0.96)";
|
||||||
|
ctx.font = `bold ${Math.max(11, Math.round(this.r * 0.39))}px ui-rounded, sans-serif`;
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
const startY = -this.r * 0.66 - (lines.length - 1) * this.r * 0.16;
|
||||||
|
lines.forEach((line, index) => ctx.fillText(line, 0, startY + index * this.r * 0.34));
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = styleNight ? "rgba(255,249,236,0.62)" : "rgba(51,34,19,0.50)";
|
ctx.fillStyle = styleNight ? "rgba(55,45,33,0.58)" : "rgba(91,64,35,0.50)";
|
||||||
ctx.font = `bold ${Math.max(8, Math.round(this.r * 0.30))}px ui-rounded, sans-serif`;
|
ctx.font = `bold ${Math.max(14, Math.round(this.r * 0.44))}px ui-rounded, sans-serif`;
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.fillText("…", 0, -this.r * 0.58);
|
ctx.fillText("…", 0, -this.r * 0.58);
|
||||||
|
|
|
||||||
85
js/render.js
85
js/render.js
|
|
@ -250,10 +250,28 @@ const terrainCache = {
|
||||||
function isCachedTerrainItem(it) {
|
function isCachedTerrainItem(it) {
|
||||||
if (!it || it.dead) return false;
|
if (!it || it.dead) return false;
|
||||||
if (it.type === "grass" || it.type === "trace" || it.type === "splat") return true;
|
if (it.type === "grass" || it.type === "trace" || it.type === "splat") return true;
|
||||||
if (it.type === "zunchi") return Math.hypot(it.vx || 0, it.vy || 0) < 0.08;
|
if (it.type === "zunchi") return false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isBehindSpriteLayerItem(it) {
|
||||||
|
return Boolean(it && !it.dead && (it.type === "bed" || it.type === "grass_bed" || it.type === "splat"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLayerSortY(entity) {
|
||||||
|
if (!entity) return 0;
|
||||||
|
if (entity.type === "bed" || entity.type === "grass_bed") return (entity.y || 0) - Math.max(10, (entity.r || 20) * 0.45);
|
||||||
|
if (typeof Tarinai !== "undefined" && entity instanceof Tarinai) return (entity.y || 0) + Math.max(6, (entity.radius || 20) * 0.28);
|
||||||
|
return entity.y || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLayerKindRank(entity) {
|
||||||
|
if (!entity) return 0;
|
||||||
|
if (entity.kind === "ant_worker" || entity.kind === "ant_queen") return 1;
|
||||||
|
if (typeof Tarinai !== "undefined" && entity instanceof Tarinai) return 2;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function terrainCacheKey(worldRef, lighting) {
|
function terrainCacheKey(worldRef, lighting) {
|
||||||
const perf = worldRef.performanceLevel ? worldRef.performanceLevel() : 0;
|
const perf = worldRef.performanceLevel ? worldRef.performanceLevel() : 0;
|
||||||
const lightBucket = Math.round((lighting.light || 0) * (perf >= 2 ? 8 : 14));
|
const lightBucket = Math.round((lighting.light || 0) * (perf >= 2 ? 8 : 14));
|
||||||
|
|
@ -737,18 +755,20 @@ function render() {
|
||||||
beginFieldTransform();
|
beginFieldTransform();
|
||||||
drawPlacementPreview(ctx, world);
|
drawPlacementPreview(ctx, world);
|
||||||
if (terrainLayer) ctx.drawImage(terrainLayer, 0, 0, sceneW, sceneH);
|
if (terrainLayer) ctx.drawImage(terrainLayer, 0, 0, sceneW, sceneH);
|
||||||
for (const ef of world.effects) {
|
|
||||||
if (ef.type === "bleed" && isEntityVisibleInRect(ef, visibleRect)) ef.draw(ctx);
|
// Low bedding is drawn one step behind the live sprite stack.
|
||||||
}
|
const backItems = [];
|
||||||
|
const layered = [];
|
||||||
for (const it of world.items) {
|
for (const it of world.items) {
|
||||||
if (isCachedTerrainItem(it)) continue;
|
if (!it || it.dead || isCachedTerrainItem(it)) continue;
|
||||||
if (isPinType(it.type) && it.pinState === "lodged") continue;
|
if (isPinType(it.type) && it.pinState === "lodged") continue;
|
||||||
if (it.isStructure && it.type === "plushie" && it.carriedById) continue;
|
if (it.isStructure && it.type === "plushie" && it.carriedById) continue;
|
||||||
if (it.type !== "zunchi" && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
|
if (!isEntityVisibleInRect(it, visibleRect)) continue;
|
||||||
}
|
if (isBehindSpriteLayerItem(it)) backItems.push(it);
|
||||||
for (const it of world.items) {
|
else layered.push({ entity: it, y: renderLayerSortY(it), rank: renderLayerKindRank(it) });
|
||||||
if (it.type === "zunchi" && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
|
|
||||||
}
|
}
|
||||||
|
backItems.sort((a, b) => renderLayerSortY(a) - renderLayerSortY(b) || (a.x || 0) - (b.x || 0));
|
||||||
|
for (const it of backItems) it.draw(ctx, world.time, lighting);
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.lineWidth = 1.15;
|
ctx.lineWidth = 1.15;
|
||||||
|
|
@ -766,10 +786,15 @@ function render() {
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
const sorted = world.sortedDrawList ? world.sortedDrawList() : world.tarinai;
|
for (const t of world.tarinai || []) {
|
||||||
for (const t of sorted) {
|
if (t && !t.dead && isEntityVisibleInRect(t, visibleRect)) layered.push({ entity: t, y: renderLayerSortY(t), rank: renderLayerKindRank(t) });
|
||||||
if (isEntityVisibleInRect(t, visibleRect)) t.draw(ctx, world.time, lighting);
|
|
||||||
}
|
}
|
||||||
|
for (const a of world.ants || []) {
|
||||||
|
if (a && !a.dead && isEntityVisibleInRect(a, visibleRect)) layered.push({ entity: a, y: renderLayerSortY(a), rank: renderLayerKindRank(a) });
|
||||||
|
}
|
||||||
|
layered.sort((a, b) => a.y - b.y || a.rank - b.rank || ((a.entity.x || 0) - (b.entity.x || 0)));
|
||||||
|
for (const entry of layered) entry.entity.draw(ctx, world.time, lighting);
|
||||||
|
|
||||||
for (const it of world.items) {
|
for (const it of world.items) {
|
||||||
if (it.isStructure && it.type === "plushie" && it.carriedById && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
|
if (it.isStructure && it.type === "plushie" && it.carriedById && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
|
||||||
}
|
}
|
||||||
|
|
@ -777,7 +802,7 @@ function render() {
|
||||||
if (isPinType(it.type) && it.pinState === "lodged" && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
|
if (isPinType(it.type) && it.pinState === "lodged" && isEntityVisibleInRect(it, visibleRect)) it.draw(ctx, world.time, lighting);
|
||||||
}
|
}
|
||||||
for (const ef of world.effects) {
|
for (const ef of world.effects) {
|
||||||
if (ef.type !== "bleed" && isEntityVisibleInRect(ef, visibleRect)) ef.draw(ctx);
|
if (isEntityVisibleInRect(ef, visibleRect)) ef.draw(ctx);
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
|
|
@ -800,7 +825,7 @@ function render() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
drawAtScreenPosition(world.selected, () => drawSelectedCard(ctx, world, lighting));
|
drawAtScreenPosition(world.selected, () => drawSelectedCard(ctx, world, lighting));
|
||||||
drawNestBoxTooltip(ctx, world);
|
drawPointerItemTooltip(ctx, world);
|
||||||
|
|
||||||
// Time HUD
|
// Time HUD
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
@ -843,23 +868,33 @@ function render() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawNestBoxTooltip(ctx, world) {
|
function drawPointerItemTooltip(ctx, world) {
|
||||||
const info = world.pointerNestBoxInfo?.();
|
const nest = world.pointerNestBoxInfo?.();
|
||||||
if (!info) return;
|
const bedInfo = !nest ? world.pointerOwnedBedInfo?.() : null;
|
||||||
const box = info.box;
|
let target = null;
|
||||||
const title = "\u5de3\u7bb1";
|
let lines = [];
|
||||||
const lines = [title];
|
if (nest?.box) {
|
||||||
|
target = nest.box;
|
||||||
|
const names = (nest.occupants || []).map(t => t?.name).filter(Boolean).slice(0, 5);
|
||||||
|
lines = ["\u5de3\u7bb1"];
|
||||||
|
if (names.length) lines.push(`${"\u5229\u7528\u4e2d"}: ${names.join("\u3001")}`);
|
||||||
|
else lines.push("\u5229\u7528\u4e2d: \u306a\u3057");
|
||||||
|
} else if (bedInfo?.bed) {
|
||||||
|
target = bedInfo.bed;
|
||||||
|
lines = ["\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9", `${"\u6301\u3061\u4e3b"}: ${bedInfo.owner?.name || "\u4e0d\u660e"}`];
|
||||||
|
}
|
||||||
|
if (!target || !lines.length) return;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.font = "12px Yomogi, sans-serif";
|
ctx.font = "12px Yomogi, sans-serif";
|
||||||
const textW = lines.reduce((m, line) => Math.max(m, ctx.measureText(line).width), 0);
|
const textW = lines.reduce((m, line) => Math.max(m, ctx.measureText(line).width), 0);
|
||||||
const lineH = 17;
|
const lineH = 17;
|
||||||
const w = Math.min(240, Math.max(92, textW + 22));
|
const w = Math.min(280, Math.max(92, textW + 22));
|
||||||
const h = Math.max(38, 16 + lines.length * lineH);
|
const h = Math.max(38, 16 + lines.length * lineH);
|
||||||
const pos = world.worldToScreen ? world.worldToScreen(box.x, box.y) : { x: box.x, y: box.y };
|
const pos = world.worldToScreen ? world.worldToScreen(target.x, target.y) : { x: target.x, y: target.y };
|
||||||
const x = clamp(pos.x - w / 2, 8, ctx.canvas.width - w - 8);
|
const x = clamp(pos.x - w / 2, 8, ctx.canvas.width - w - 8);
|
||||||
const y = clamp(pos.y - box.r * 1.8 - h, 8, ctx.canvas.height - h - 8);
|
const y = clamp(pos.y - (target.r || 24) * 1.8 - h, 8, ctx.canvas.height - h - 8);
|
||||||
ctx.fillStyle = "rgba(46,38,30,0.62)";
|
ctx.fillStyle = "rgba(46,38,30,0.66)";
|
||||||
ctx.strokeStyle = "rgba(255,248,220,0.72)";
|
ctx.strokeStyle = "rgba(255,248,220,0.74)";
|
||||||
ctx.lineWidth = 1.2;
|
ctx.lineWidth = 1.2;
|
||||||
roundedRect(ctx, x, y, w, h, 10);
|
roundedRect(ctx, x, y, w, h, 10);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
|
||||||
|
|
@ -82,18 +82,19 @@
|
||||||
dialog.setAttribute("aria-modal", "true");
|
dialog.setAttribute("aria-modal", "true");
|
||||||
dialog.innerHTML = `
|
dialog.innerHTML = `
|
||||||
<div class="field-dialog-panel save-dialog-panel">
|
<div class="field-dialog-panel save-dialog-panel">
|
||||||
<h2 id="saveDialogTitle">セーブ / ロード</h2>
|
<h2 id="saveDialogTitle">\u30bb\u30fc\u30d6 / \u30ed\u30fc\u30c9</h2>
|
||||||
<p class="hint">9つのスロットに現在のコロニーを保存できます。ハッシュテキストは別端末や別ブラウザへの移動に使えます。</p>
|
<p class="hint save-warning">\u30bb\u30fc\u30d6\u6a5f\u80fd\u306f\u5b9f\u9a13\u4e2d\u3067\u3042\u308a\u3001\u4fdd\u8a3c\u5bfe\u8c61\u5916\u3067\u3059\u3002</p>
|
||||||
|
<p class="hint">9\u3064\u306e\u30b9\u30ed\u30c3\u30c8\u306b\u73fe\u5728\u306e\u30b3\u30ed\u30cb\u30fc\u3092\u4fdd\u5b58\u3067\u304d\u307e\u3059\u3002\u30cf\u30c3\u30b7\u30e5\u30c6\u30ad\u30b9\u30c8\u306f\u5225\u7aef\u672b\u3084\u5225\u30d6\u30e9\u30a6\u30b6\u3078\u306e\u79fb\u52d5\u306b\u4f7f\u3048\u307e\u3059\u3002</p>
|
||||||
<div id="saveSlotList" class="save-slot-list"></div>
|
<div id="saveSlotList" class="save-slot-list"></div>
|
||||||
<div class="save-export-panel">
|
<div class="save-export-panel">
|
||||||
<div class="save-export-actions">
|
<div class="save-export-actions">
|
||||||
<button id="saveExportBtn" class="btn" type="button">現在の状態を書き出し</button>
|
<button id="saveExportBtn" class="btn" type="button">\u73fe\u5728\u306e\u72b6\u614b\u3092\u66f8\u304d\u51fa\u3057</button>
|
||||||
<button id="saveImportBtn" class="btn primary" type="button">ハッシュテキストを読み込み</button>
|
<button id="saveImportBtn" class="btn primary" type="button">\u30cf\u30c3\u30b7\u30e5\u30c6\u30ad\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307f</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="saveHashText" class="save-hash-text" spellcheck="false" placeholder="ここにハッシュテキストが表示されます。読み込む場合はここに貼り付けてください。"></textarea>
|
<textarea id="saveHashText" class="save-hash-text" spellcheck="false" placeholder="\u3053\u3053\u306b\u30cf\u30c3\u30b7\u30e5\u30c6\u30ad\u30b9\u30c8\u304c\u8868\u793a\u3055\u308c\u307e\u3059\u3002\u8aad\u307f\u8fbc\u3080\u5834\u5408\u306f\u3053\u3053\u306b\u8cbc\u308a\u4ed8\u3051\u3066\u304f\u3060\u3055\u3044\u3002"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="field-dialog-actions">
|
<div class="field-dialog-actions">
|
||||||
<button id="saveCloseBtn" class="btn" type="button">閉じる</button>
|
<button id="saveCloseBtn" class="btn" type="button">\u9589\u3058\u308b</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.body.appendChild(dialog);
|
document.body.appendChild(dialog);
|
||||||
|
|
@ -110,12 +111,12 @@
|
||||||
const row = document.createElement("div");
|
const row = document.createElement("div");
|
||||||
row.className = "save-slot-row";
|
row.className = "save-slot-row";
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<div class="save-slot-title">スロット ${i}</div>
|
<div class="save-slot-title">\u30b9\u30ed\u30c3\u30c8 ${i}</div>
|
||||||
<div class="save-slot-meta">${htmlEscape(formatSlotSummary(snapshot))}</div>
|
<div class="save-slot-meta">${htmlEscape(formatSlotSummary(snapshot))}</div>
|
||||||
<div class="save-slot-actions">
|
<div class="save-slot-actions">
|
||||||
<button class="btn mini" data-save-slot="${i}" type="button">保存</button>
|
<button class="btn mini" data-save-slot="${i}" type="button">\u4fdd\u5b58</button>
|
||||||
<button class="btn mini" data-load-slot="${i}" type="button" ${snapshot ? "" : "disabled"}>読込</button>
|
<button class="btn mini" data-load-slot="${i}" type="button" ${snapshot ? "" : "disabled"}>\u8aad\u8fbc</button>
|
||||||
<button class="btn mini danger" data-delete-slot="${i}" type="button" ${snapshot ? "" : "disabled"}>削除</button>
|
<button class="btn mini danger" data-delete-slot="${i}" type="button" ${snapshot ? "" : "disabled"}>\u524a\u9664</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
list.appendChild(row);
|
list.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
@ -151,21 +152,21 @@
|
||||||
saveSlot(slot);
|
saveSlot(slot);
|
||||||
renderSlotList();
|
renderSlotList();
|
||||||
global.audio?.notify?.();
|
global.audio?.notify?.();
|
||||||
global.showToast?.(`スロット${slot}に保存しました。`);
|
global.showToast?.(`\u30b9\u30ed\u30c3\u30c8${slot}に\u4fdd\u5b58しました。`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
global.showToast?.("保存に失敗しました。空き容量を確認してください。");
|
global.showToast?.("\u4fdd\u5b58に失敗しました。空き容量を確認してください。");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (loadBtn) {
|
if (loadBtn) {
|
||||||
const slot = Number(loadBtn.dataset.loadSlot);
|
const slot = Number(loadBtn.dataset.loadSlot);
|
||||||
if (!global.confirm?.(`スロット${slot}を読み込みます。現在の状態は上書きされます。`)) return;
|
if (!global.confirm?.(`\u30b9\u30ed\u30c3\u30c8${slot}を読み込みます。現在の状態は上書きされます。`)) return;
|
||||||
try {
|
try {
|
||||||
loadSlot(slot);
|
loadSlot(slot);
|
||||||
closeDialog();
|
closeDialog();
|
||||||
global.audio?.notify?.();
|
global.audio?.notify?.();
|
||||||
global.showToast?.(`スロット${slot}を読み込みました。`);
|
global.showToast?.(`\u30b9\u30ed\u30c3\u30c8${slot}を読み込みました。`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(error);
|
console.warn(error);
|
||||||
global.showToast?.("読み込みに失敗しました。");
|
global.showToast?.("読み込みに失敗しました。");
|
||||||
|
|
@ -174,11 +175,11 @@
|
||||||
}
|
}
|
||||||
if (deleteBtn) {
|
if (deleteBtn) {
|
||||||
const slot = Number(deleteBtn.dataset.deleteSlot);
|
const slot = Number(deleteBtn.dataset.deleteSlot);
|
||||||
if (!global.confirm?.(`スロット${slot}を削除しますか?`)) return;
|
if (!global.confirm?.(`\u30b9\u30ed\u30c3\u30c8${slot}を\u524a\u9664しますか?`)) return;
|
||||||
deleteSlot(slot);
|
deleteSlot(slot);
|
||||||
renderSlotList();
|
renderSlotList();
|
||||||
global.audio?.delete?.();
|
global.audio?.delete?.();
|
||||||
global.showToast?.(`スロット${slot}を削除しました。`);
|
global.showToast?.(`\u30b9\u30ed\u30c3\u30c8${slot}を\u524a\u9664しました。`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.target.closest("#saveCloseBtn")) closeDialog();
|
if (e.target.closest("#saveCloseBtn")) closeDialog();
|
||||||
|
|
@ -195,7 +196,7 @@
|
||||||
const area = dialog.querySelector("#saveHashText");
|
const area = dialog.querySelector("#saveHashText");
|
||||||
const text = area?.value || "";
|
const text = area?.value || "";
|
||||||
if (!text.trim()) { global.showToast?.("読み込むハッシュテキストを貼り付けてください。"); return; }
|
if (!text.trim()) { global.showToast?.("読み込むハッシュテキストを貼り付けてください。"); return; }
|
||||||
if (!global.confirm?.("ハッシュテキストを読み込みます。現在の状態は上書きされます。")) return;
|
if (!global.confirm?.("\u30cf\u30c3\u30b7\u30e5\u30c6\u30ad\u30b9\u30c8\u3092\u8aad\u307f\u8fbc\u307fます。現在の状態は上書きされます。")) return;
|
||||||
try {
|
try {
|
||||||
restoreSnapshot(decodeSnapshot(text), global.world);
|
restoreSnapshot(decodeSnapshot(text), global.world);
|
||||||
closeDialog();
|
closeDialog();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
this.usedCount = 0;
|
this.usedCount = 0;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.r = definition.type === "plushie" ? 13 : 24;
|
this.r = definition.type === "plushie" ? 13 : (definition.type === "grass_bed" ? 18 : 24);
|
||||||
this.amount = this.hp;
|
this.amount = this.hp;
|
||||||
this.seed = Math.random() * 1000;
|
this.seed = Math.random() * 1000;
|
||||||
this.roles = clonePlain(definition.roles);
|
this.roles = clonePlain(definition.roles);
|
||||||
|
|
@ -78,21 +78,33 @@
|
||||||
ctx.translate(this.x, this.y);
|
ctx.translate(this.x, this.y);
|
||||||
const night = (lighting?.light || 1) < 0.42;
|
const night = (lighting?.light || 1) < 0.42;
|
||||||
if (this.type === "grass_bed") {
|
if (this.type === "grass_bed") {
|
||||||
ctx.fillStyle = night ? "rgba(108,129,77,0.88)" : "rgba(134,174,83,0.92)";
|
// かんたんベッド: 緑色の小さい干草寝床。通常の干草寝床より一回り小さく、草束感を強める。
|
||||||
ctx.strokeStyle = night ? "rgba(55,73,49,0.72)" : "rgba(72,105,50,0.72)";
|
const grassFill = night ? "rgba(78,132,67,0.90)" : "rgba(84,177,72,0.93)";
|
||||||
|
const grassStroke = night ? "rgba(39,78,43,0.76)" : "rgba(45,112,48,0.78)";
|
||||||
|
const strawStroke = night ? "rgba(168,207,119,0.42)" : "rgba(205,234,128,0.66)";
|
||||||
|
ctx.fillStyle = grassFill;
|
||||||
|
ctx.strokeStyle = grassStroke;
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.ellipse(0, 4, this.r * 1.55, this.r * 0.62, -0.08, 0, Math.PI * 2);
|
ctx.ellipse(0, 4, this.r * 1.72, this.r * 0.66, -0.08, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.strokeStyle = night ? "rgba(185,203,142,0.35)" : "rgba(231,229,139,0.55)";
|
ctx.strokeStyle = strawStroke;
|
||||||
for (let i = 0; i < 12; i++) {
|
ctx.lineWidth = 1.45;
|
||||||
const a = -0.8 + i * 0.145;
|
for (let i = 0; i < 15; i++) {
|
||||||
|
const x = -this.r * 1.14 + i * this.r * 0.16;
|
||||||
|
const y = this.r * 0.38 + Math.sin(i * 1.7 + this.seed) * 1.8;
|
||||||
|
const endX = x + Math.cos(-0.55 + i * 0.08) * this.r * 0.74;
|
||||||
|
const endY = -this.r * 0.30 + Math.sin(time * 0.7 + i) * 2.6;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(-this.r * 1.05 + i * this.r * 0.19, this.r * 0.38);
|
ctx.moveTo(x, y);
|
||||||
ctx.quadraticCurveTo(Math.sin(time + i) * 4, -this.r * 0.12, Math.cos(a) * this.r * 0.92, -this.r * 0.42 + Math.sin(i) * 4);
|
ctx.quadraticCurveTo(Math.sin(time + i) * 3.2, -this.r * 0.10, endX, endY);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
ctx.fillStyle = night ? "rgba(42,93,42,0.34)" : "rgba(66,145,47,0.36)";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(0, this.r * 0.22, this.r * 1.28, this.r * 0.28, -0.06, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
} else if (this.type === "plushie") {
|
} else if (this.type === "plushie") {
|
||||||
const img = typeof getRenderableImage === "function" ? getRenderableImage("smile", "smile") : global.images?.get?.("smile");
|
const img = typeof getRenderableImage === "function" ? getRenderableImage("smile", "smile") : global.images?.get?.("smile");
|
||||||
ctx.rotate(Math.sin(time * 2 + this.seed) * 0.05);
|
ctx.rotate(Math.sin(time * 2 + this.seed) * 0.05);
|
||||||
|
|
@ -158,7 +170,7 @@
|
||||||
|
|
||||||
StructureRegistry.register({
|
StructureRegistry.register({
|
||||||
type: "grass_bed",
|
type: "grass_bed",
|
||||||
label: "\u8349\u306e\u7c21\u6613\u30d9\u30c3\u30c9",
|
label: "\u304b\u3093\u305f\u3093\u30d9\u30c3\u30c9",
|
||||||
buildNeed: "fulfill",
|
buildNeed: "fulfill",
|
||||||
buildTime: 4,
|
buildTime: 4,
|
||||||
maxHp: 100,
|
maxHp: 100,
|
||||||
|
|
|
||||||
|
|
@ -52,17 +52,34 @@ class Tarinai { constructor(world, opts = {}) {
|
||||||
this.hasPaired = opts.hasPaired ?? false;
|
this.hasPaired = opts.hasPaired ?? false;
|
||||||
this.hunger = opts.hunger ?? rand(16, 45);
|
this.hunger = opts.hunger ?? rand(16, 45);
|
||||||
this.loneliness = opts.loneliness ?? rand(18, 62);
|
this.loneliness = opts.loneliness ?? rand(18, 62);
|
||||||
this.energy = opts.energy ?? rand(54, 98);
|
const initialDayP = world?.dayProgress?.() || 0;
|
||||||
this.needs = opts.needs && typeof opts.needs === "object" && typeof createDefaultNeeds === "function" ? { ...createDefaultNeeds(), ...opts.needs } : (typeof createDefaultNeeds === "function" ? createDefaultNeeds() : { food: 0, sleep: 0, health: 0, safety: 0, social: 0, fulfill: 0 });
|
const initialNight = initialDayP >= 0.66 || initialDayP < 0.18;
|
||||||
this.stress = typeof calculateStressFromNeeds === "function" ? calculateStressFromNeeds(this.needs) : 0;
|
const initialAfternoon = initialDayP >= 0.46 && initialDayP < 0.58;
|
||||||
this.previousNeeds = opts.previousNeeds && typeof opts.previousNeeds === "object" && typeof createDefaultNeeds === "function" ? { ...createDefaultNeeds(), ...opts.previousNeeds } : { ...this.needs };
|
this.energy = opts.energy ?? (initialNight ? rand(34, 68) : (initialAfternoon ? rand(50, 82) : rand(66, 98)));
|
||||||
|
this.circadianSleepPressure = Number.isFinite(opts.circadianSleepPressure) ? opts.circadianSleepPressure : (initialNight ? rand(58, 78) : (initialAfternoon ? rand(8, 18) : rand(0, 5)));
|
||||||
|
const defaultNeeds = typeof createDefaultNeeds === "function" ? createDefaultNeeds() : { food: 0, sleep: 0, health: 0, safety: 0, social: 0, fulfill: 0 };
|
||||||
|
this.needs = opts.needs && typeof opts.needs === "object" ? { ...defaultNeeds, ...opts.needs } : { ...defaultNeeds };
|
||||||
|
this.needDisplay = opts.needDisplay && typeof opts.needDisplay === "object" ? { ...defaultNeeds, ...opts.needDisplay } : { ...this.needs };
|
||||||
|
this.needRaw = opts.needRaw && typeof opts.needRaw === "object" ? { ...defaultNeeds, ...opts.needRaw } : { ...this.needs };
|
||||||
|
this.needRawBase = opts.needRawBase && typeof opts.needRawBase === "object" ? { ...defaultNeeds, ...opts.needRawBase } : { ...this.needRaw };
|
||||||
|
this.needSatisfaction = opts.needSatisfaction && typeof opts.needSatisfaction === "object" ? { ...defaultNeeds, ...opts.needSatisfaction } : { ...defaultNeeds };
|
||||||
|
this.lastSatisfiedAt = opts.lastSatisfiedAt && typeof opts.lastSatisfiedAt === "object" ? { ...opts.lastSatisfiedAt } : {};
|
||||||
|
this.lastSatisfactionSource = opts.lastSatisfactionSource || "";
|
||||||
|
this.stress = typeof calculateStressFromNeeds === "function" ? calculateStressFromNeeds(this.needRaw || this.needs) : 0;
|
||||||
|
this.previousNeeds = opts.previousNeeds && typeof opts.previousNeeds === "object" ? { ...defaultNeeds, ...opts.previousNeeds } : { ...this.needs };
|
||||||
this.intent = opts.intent && typeof opts.intent === "object" ? { ...opts.intent } : null;
|
this.intent = opts.intent && typeof opts.intent === "object" ? { ...opts.intent } : null;
|
||||||
|
this.currentAction = opts.currentAction && typeof opts.currentAction === "object" ? { ...opts.currentAction } : null;
|
||||||
|
this.activeBehavior = opts.activeBehavior && typeof opts.activeBehavior === "object" ? { ...opts.activeBehavior } : null;
|
||||||
|
this.forcedBehaviorQueue = Array.isArray(opts.forcedBehaviorQueue) ? opts.forcedBehaviorQueue.map(v => ({ ...(v || {}) })).slice(-8) : [];
|
||||||
|
this.behaviorSerial = opts.behaviorSerial ?? 0;
|
||||||
|
this.actionCooldowns = opts.actionCooldowns && typeof opts.actionCooldowns === "object" ? { ...opts.actionCooldowns } : {};
|
||||||
this.needShock = opts.needShock && typeof opts.needShock === "object" ? { ...opts.needShock } : {};
|
this.needShock = opts.needShock && typeof opts.needShock === "object" ? { ...opts.needShock } : {};
|
||||||
this.buildPlan = opts.buildPlan && typeof opts.buildPlan === "object" ? { ...opts.buildPlan } : null;
|
this.buildPlan = opts.buildPlan && typeof opts.buildPlan === "object" ? { ...opts.buildPlan } : null;
|
||||||
|
this.sleepSession = opts.sleepSession && typeof opts.sleepSession === "object" ? { ...opts.sleepSession } : null;
|
||||||
this.hpBarTimer = opts.hpBarTimer ?? 0;
|
this.hpBarTimer = opts.hpBarTimer ?? 0;
|
||||||
this.stressBarTimer = opts.stressBarTimer ?? 0;
|
this.stressBarTimer = opts.stressBarTimer ?? 0;
|
||||||
this.affection = opts.affection ?? rand(0, 30);
|
this.affection = opts.affection ?? rand(0, 30);
|
||||||
this.need = opts.need || pick(NEEDS);
|
this.need = "";
|
||||||
this.state = opts.state || "idle";
|
this.state = opts.state || "idle";
|
||||||
this.dead = !!opts.dead;
|
this.dead = !!opts.dead;
|
||||||
this.deathReason = opts.deathReason || "";
|
this.deathReason = opts.deathReason || "";
|
||||||
|
|
@ -83,6 +100,7 @@ class Tarinai { constructor(world, opts = {}) {
|
||||||
this.panicTargetUntil = opts.panicTargetUntil ?? 0;
|
this.panicTargetUntil = opts.panicTargetUntil ?? 0;
|
||||||
this.panicStuckTimer = opts.panicStuckTimer ?? 0;
|
this.panicStuckTimer = opts.panicStuckTimer ?? 0;
|
||||||
this.wanderAngle = rand(0, Math.PI * 2);
|
this.wanderAngle = rand(0, Math.PI * 2);
|
||||||
|
this.wanderTarget = opts.wanderTarget && typeof opts.wanderTarget === "object" ? { ...opts.wanderTarget } : null;
|
||||||
this.reproductionTimer = rand(4, CONFIG.reproductionCooldown);
|
this.reproductionTimer = rand(4, CONFIG.reproductionCooldown);
|
||||||
this.lastLog = 0;
|
this.lastLog = 0;
|
||||||
this.sleeping = false;
|
this.sleeping = false;
|
||||||
|
|
@ -95,7 +113,9 @@ class Tarinai { constructor(world, opts = {}) {
|
||||||
this.visualFacing = opts.visualFacing ?? this.facing;
|
this.visualFacing = opts.visualFacing ?? this.facing;
|
||||||
this.facingLockUntil = opts.facingLockUntil ?? 0;
|
this.facingLockUntil = opts.facingLockUntil ?? 0;
|
||||||
this.eatTimer = opts.eatTimer ?? 0;
|
this.eatTimer = opts.eatTimer ?? 0;
|
||||||
this.eatCooldown = 0;
|
this.eatCooldown = opts.eatCooldown ?? 0;
|
||||||
|
this.lastAteAt = Number.isFinite(opts.lastAteAt) ? opts.lastAteAt : (this.world?.time || 0);
|
||||||
|
this.mealCooldownUntil = Number.isFinite(opts.mealCooldownUntil) ? opts.mealCooldownUntil : 0;
|
||||||
this.foodReactTimer = opts.foodReactTimer ?? 0;
|
this.foodReactTimer = opts.foodReactTimer ?? 0;
|
||||||
this.surpriseTimer = opts.surpriseTimer ?? 0;
|
this.surpriseTimer = opts.surpriseTimer ?? 0;
|
||||||
this.fearTimer = opts.fearTimer ?? 0;
|
this.fearTimer = opts.fearTimer ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,31 @@
|
||||||
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
|
Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({
|
||||||
setActionState(state, opts = {}) {
|
setActionState(state, opts = {}) {
|
||||||
if (this.dead) return false;
|
if (this.dead) return false;
|
||||||
this.state = state || "idle";
|
const nextState = state || "idle";
|
||||||
|
this.state = nextState;
|
||||||
if ("target" in opts) this.target = opts.target;
|
if ("target" in opts) this.target = opts.target;
|
||||||
if (opts.reason != null) this.thought = String(opts.reason || "");
|
if (opts.reason != null) this.thought = String(opts.reason || "");
|
||||||
if (opts.clearTarget) this.target = null;
|
if (opts.clearTarget) this.target = null;
|
||||||
if (opts.sleeping != null) this.sleeping = Boolean(opts.sleeping);
|
if (opts.sleeping != null) this.sleeping = Boolean(opts.sleeping);
|
||||||
if (opts.wake) {
|
if (opts.wake) {
|
||||||
this.sleeping = false;
|
this.sleeping = false;
|
||||||
|
this.sleepSession = null;
|
||||||
if (this.insideNestBoxId && this.leaveNestBox) this.leaveNestBox(this.world?.nestBoxById?.(this.insideNestBoxId));
|
if (this.insideNestBoxId && this.leaveNestBox) this.leaveNestBox(this.world?.nestBoxById?.(this.insideNestBoxId));
|
||||||
this.insideNestBoxId = "";
|
this.insideNestBoxId = "";
|
||||||
}
|
}
|
||||||
|
if (nextState !== "sleep" && opts.sleeping === false) this.sleepSession = null;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
beginEating(item = null, reason = "") {
|
beginEating(item = null, reason = "") {
|
||||||
return this.setActionState("eat", {
|
const text = reason || (item?.type && typeof toolLabel === "function" ? `${toolLabel(item.type)}を食べている` : "食べている");
|
||||||
|
const ok = this.setActionState("eat", {
|
||||||
target: item || null,
|
target: item || null,
|
||||||
reason: reason || (item?.type && typeof toolLabel === "function" ? `${toolLabel(item.type)}を食べている` : "食べている"),
|
reason: text,
|
||||||
sleeping: false,
|
sleeping: false,
|
||||||
});
|
});
|
||||||
|
if (typeof setLiveActionText === "function") setLiveActionText(this, { need: "food", actionId: "eat_food", actionLabel: "食べている", reasonText: text, target: item || null, phase: "perform", source: "behavior" });
|
||||||
|
return ok;
|
||||||
},
|
},
|
||||||
|
|
||||||
currentLodgedPin() {
|
currentLodgedPin() {
|
||||||
|
|
@ -42,17 +48,43 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
goIdle(reason = "") {
|
goIdle(reason = "") {
|
||||||
|
if (typeof clearActiveBehavior === "function") clearActiveBehavior(this, reason || this.thought || "待っている");
|
||||||
return this.setActionState("idle", { target: null, reason: reason || this.thought || "待っている", sleeping: false });
|
return this.setActionState("idle", { target: null, reason: reason || this.thought || "待っている", sleeping: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
startSleeping(target = null, reason = "") {
|
startSleeping(target = null, reason = "") {
|
||||||
return this.setActionState("sleep", { target, reason: reason || (target?.type === "nest_box" ? "巣箱の中で眠っている" : "眠っている"), sleeping: true });
|
const now = this.world?.time || 0;
|
||||||
|
if (!this.sleepSession) {
|
||||||
|
const inFurniture = Boolean(target);
|
||||||
|
this.sleepSession = {
|
||||||
|
startedAt: now,
|
||||||
|
minDuration: inFurniture ? rand(16, 28) : rand(10, 18),
|
||||||
|
targetEnergy: inFurniture ? rand(84, 94) : rand(74, 86),
|
||||||
|
maxDuration: inFurniture ? rand(42, 68) : rand(28, 48),
|
||||||
|
targetId: target?.id || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.intent = this.intent || { need: "sleep", tiedNeeds: ["sleep"], actionId: target ? "sleep_in_bed" : "sleep_anywhere", actionLabel: reason || "眠っている", reasonText: "ねむいので、眠っている。", startedAt: now };
|
||||||
|
this.currentAction = this.currentAction || { id: this.intent.actionId || "sleep", need: "sleep", startedAt: this.sleepSession.startedAt, minDuration: this.sleepSession.minDuration, lockSeconds: this.sleepSession.minDuration };
|
||||||
|
const text = reason || (target?.type === "nest_box" ? "巣箱の中で眠っている" : "眠っている");
|
||||||
|
if (typeof setLiveActionText === "function") setLiveActionText(this, { need: "sleep", actionId: target ? "sleep_in_bed" : "sleep_anywhere", actionLabel: "眠っている", reasonText: text, target, phase: "perform", source: "behavior" });
|
||||||
|
return this.setActionState("sleep", { target, reason: text, sleeping: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
seekTarget(state, target = null, reason = "") {
|
seekTarget(state, target = null, reason = "") {
|
||||||
return this.setActionState(state, { target, reason, sleeping: false });
|
return this.setActionState(state, { target, reason, sleeping: false });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
forceBehavior(actionId = "", opts = {}) {
|
||||||
|
if (typeof queueForcedTarinaiBehavior !== "function") return false;
|
||||||
|
return queueForcedTarinaiBehavior(this, actionId, opts);
|
||||||
|
},
|
||||||
|
|
||||||
|
currentBehaviorText() {
|
||||||
|
if (typeof activeBehaviorText === "function") return activeBehaviorText(this);
|
||||||
|
return String(this.activeBehavior?.presentText || this.activeBehavior?.reasonText || this.thought || "").trim();
|
||||||
|
},
|
||||||
|
|
||||||
enterPanic(opts = {}) {
|
enterPanic(opts = {}) {
|
||||||
if (this.dead) return false;
|
if (this.dead) return false;
|
||||||
const profile = this.personalityProfile ? this.personalityProfile() : { fear: 1 };
|
const profile = this.personalityProfile ? this.personalityProfile() : { fear: 1 };
|
||||||
|
|
|
||||||
|
|
@ -216,9 +216,17 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
applyFoodHungerRelief(amount = 0) {
|
applyFoodHungerRelief(amount = 0) {
|
||||||
if (this.hasItemEffect("laxative")) return (Number(amount) || 0) * (EFFECT_REGISTRY?.modifier?.("laxative", "hungerReliefMultiplier", 0) ?? 0);
|
const raw = Math.max(0, Number(amount) || 0);
|
||||||
this.hunger -= Number(amount) || 0;
|
const actual = this.hasItemEffect("laxative") ? raw * (EFFECT_REGISTRY?.modifier?.("laxative", "hungerReliefMultiplier", 0) ?? 0) : raw;
|
||||||
return amount;
|
if (actual > 0.2) this.lastAteAt = this.world?.time || 0;
|
||||||
|
if (actual >= 6) {
|
||||||
|
const day = Number(this.world?.config?.dayLength || CONFIG?.dayLength || 120) || 120;
|
||||||
|
const base = day / 4;
|
||||||
|
const jitter = typeof stableUnit === "function" ? (0.82 + stableUnit(this.familyKey || this.id, "meal-cooldown") * 0.36) : 1;
|
||||||
|
this.mealCooldownUntil = Math.max(this.mealCooldownUntil || 0, (this.world?.time || 0) + base * jitter);
|
||||||
|
}
|
||||||
|
this.hunger -= actual;
|
||||||
|
return actual;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateItemEffects(dt = 0) {
|
updateItemEffects(dt = 0) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -16,7 +16,7 @@
|
||||||
const gait = this.state === "sleep" ? 0.8 : clamp(moveSpeed / 42, 0.2, 1.8);
|
const gait = this.state === "sleep" ? 0.8 : clamp(moveSpeed / 42, 0.2, 1.8);
|
||||||
const breathing = Math.sin(t * (this.state === "sleep" ? 1.1 : 2.3) + this.age * 0.18);
|
const breathing = Math.sin(t * (this.state === "sleep" ? 1.1 : 2.3) + this.age * 0.18);
|
||||||
const headbuttPulse = this.state === "fight" ? Math.max(0, Math.sin(t * 18 + this.age * 0.37)) : 0;
|
const headbuttPulse = this.state === "fight" ? Math.max(0, Math.sin(t * 18 + this.age * 0.37)) : 0;
|
||||||
const eatingShake = (this.eatTimer > 0.02 || this.grassEatTimer > 0) ? Math.sin(t * 54 + this.age) * clamp(Math.max(this.eatTimer / 0.32, this.grassEatTimer / 0.22), 0, 1) : 0;
|
const eatingShake = (this.eatTimer > 0.02 || this.grassEatTimer > 0) ? Math.sin(t * 62 + this.age) * clamp(Math.max(this.eatTimer / 1.0, this.grassEatTimer / 1.0), 0, 1) : 0;
|
||||||
const fallPose = this.fallTimer > 0 ? clamp(this.fallTimer / Math.max(this.fallMax || 1, 0.01), 0, 1) : 0;
|
const fallPose = this.fallTimer > 0 ? clamp(this.fallTimer / Math.max(this.fallMax || 1, 0.01), 0, 1) : 0;
|
||||||
const fallLift = Math.sin(fallPose * Math.PI) * this.radius * 0.20;
|
const fallLift = Math.sin(fallPose * Math.PI) * this.radius * 0.20;
|
||||||
const intimidateShake = this.intimidateTimer > 0.04 ? Math.sin(t * 44 + this.age) * this.radius * 0.09 : 0;
|
const intimidateShake = this.intimidateTimer > 0.04 ? Math.sin(t * 44 + this.age) * this.radius * 0.09 : 0;
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
const blastSpin = this.blastSpinTimer > 0.04 ? this.fallDir * ((this.blastSpinMax || 1) - this.blastSpinTimer) * Math.PI * 70.0 : 0;
|
const blastSpin = this.blastSpinTimer > 0.04 ? this.fallDir * ((this.blastSpinMax || 1) - this.blastSpinTimer) * Math.PI * 70.0 : 0;
|
||||||
const fallSpin = this.blastSpinTimer > 0.04 ? blastSpin : (fallPose > 0 ? this.fallDir * (1 - fallPose) * Math.PI * 2 : 0);
|
const fallSpin = this.blastSpinTimer > 0.04 ? blastSpin : (fallPose > 0 ? this.fallDir * (1 - fallPose) * Math.PI * 2 : 0);
|
||||||
const diseaseSpin = this.fightDisease ? (t * 3.6 + this.age) : 0;
|
const diseaseSpin = this.fightDisease ? (t * 3.6 + this.age) : 0;
|
||||||
const motionTilt = clamp(this.vx / 240, -0.22, 0.22) + (this.state === "fight" ? headbuttPulse * 0.05 : 0) + eatingShake * 0.105 + this.fallDir * fallPose * 0.18 + fallSpin + diseaseSpin;
|
const motionTilt = clamp(this.vx / 240, -0.22, 0.22) + (this.state === "fight" ? headbuttPulse * 0.05 : 0) + eatingShake * 0.145 + this.fallDir * fallPose * 0.18 + fallSpin + diseaseSpin;
|
||||||
const insideCardboard = false;
|
const insideCardboard = false;
|
||||||
const visualScale = this.effectiveScale ? this.effectiveScale() : this.scale;
|
const visualScale = this.effectiveScale ? this.effectiveScale() : this.scale;
|
||||||
let drawW = w * visualScale * 0.30;
|
let drawW = w * visualScale * 0.30;
|
||||||
|
|
@ -212,11 +212,11 @@
|
||||||
|
|
||||||
if (this.eatTimer > 0.02) {
|
if (this.eatTimer > 0.02) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalAlpha = 0.46 * (this.eatTimer / 0.32);
|
ctx.globalAlpha = 0.46 * clamp(this.eatTimer / 1.0, 0, 1);
|
||||||
ctx.strokeStyle = this.lastMealColor || "#f7cf67";
|
ctx.strokeStyle = this.lastMealColor || "#f7cf67";
|
||||||
ctx.lineWidth = 1.6;
|
ctx.lineWidth = 1.6;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(0, -drawH * 0.02, drawW * 0.22 + (1 - this.eatTimer / 0.32) * 5, 0, Math.PI * 2);
|
ctx.arc(0, -drawH * 0.02, drawW * 0.22 + (1 - clamp(this.eatTimer / 1.0, 0, 1)) * 5, 0, Math.PI * 2);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,16 @@
|
||||||
|
|
||||||
const fightMochiContact = this.world.canFightPair?.(this, o) && (this.hasFightMochiEffect() || o.hasFightMochiEffect());
|
const fightMochiContact = this.world.canFightPair?.(this, o) && (this.hasFightMochiEffect() || o.hasFightMochiEffect());
|
||||||
if (fightMochiContact && d <= Math.max(42, this.radius + o.radius + 16)) {
|
if (fightMochiContact && d <= Math.max(42, this.radius + o.radius + 16)) {
|
||||||
if (this.world.startForcedFight?.(this, o)) continue;
|
this.conflictTargetId = o.id;
|
||||||
|
o.conflictTargetId = this.id;
|
||||||
|
this.conflictUrge = Math.max(this.conflictUrge || 0, 92);
|
||||||
|
o.conflictUrge = Math.max(o.conflictUrge || 0, 82);
|
||||||
|
if (typeof applyNeedShock === "function") applyNeedShock(this, { social: dt * 36, safety: dt * 5 }, o);
|
||||||
|
if (typeof applyNeedShock === "function") applyNeedShock(o, { social: dt * 30, safety: dt * 5 }, this);
|
||||||
|
if (typeof queueForcedTarinaiBehavior === "function") {
|
||||||
|
queueForcedTarinaiBehavior(this, "fight_rival", { target: o, source: "fight_mochi", priority: 98, ttl: 8, reasonText: `${o.name || "相手"}と喧嘩しようとしている` });
|
||||||
|
queueForcedTarinaiBehavior(o, "fight_rival", { target: this, source: "fight_mochi", priority: 98, ttl: 8, reasonText: `${this.name || "相手"}と喧嘩しようとしている` });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.zunchiDisease && !o.zunchiDisease && (o.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && Math.random() < (o.diseaseChance ? o.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1))) {
|
if (this.zunchiDisease && !o.zunchiDisease && (o.zunchiDiseaseCooldown || 0) <= 0 && d < 54 && Math.random() < (o.diseaseChance ? o.diseaseChance(dt * 0.065 * clamp(1 - d / 58, 0.12, 1)) : dt * 0.065 * clamp(1 - d / 58, 0.12, 1))) {
|
||||||
|
|
@ -34,14 +43,11 @@
|
||||||
const ny = (nonSlave.y - slave.y) / d;
|
const ny = (nonSlave.y - slave.y) / d;
|
||||||
nonSlave.vx += nx * dt * 34;
|
nonSlave.vx += nx * dt * 34;
|
||||||
nonSlave.vy += ny * dt * 34;
|
nonSlave.vy += ny * dt * 34;
|
||||||
if (nonSlave.state === "idle" || nonSlave.state === "seek_friend") {
|
if (typeof applyNeedShock === "function") applyNeedShock(nonSlave, { safety: dt * 24, social: dt * 5 }, slave);
|
||||||
if (nonSlave.enterPanic) nonSlave.enterPanic({ threat: slave, reason: "\u305a\u3093\u3061\u3069\u308c\u3044\u304c\u8fd1\u3044", fear: 0.38, wake: false, cause: "zunchi_slave_nearby" });
|
nonSlave.lastNeedShockBreaker = slave;
|
||||||
else { nonSlave.fearTimer = Math.max(nonSlave.fearTimer || 0, 0.38 * nonSlave.personalityProfile().fear); nonSlave.target = slave; nonSlave.thought = "\u305a\u3093\u3061\u3069\u308c\u3044\u304c\u8fd1\u3044"; }
|
nonSlave.fearTimer = Math.max(nonSlave.fearTimer || 0, 0.22 * nonSlave.personalityProfile().fear);
|
||||||
} else {
|
nonSlave.target = slave;
|
||||||
nonSlave.fearTimer = Math.max(nonSlave.fearTimer || 0, 0.38 * nonSlave.personalityProfile().fear);
|
nonSlave.thought = "\u305a\u3093\u3061\u3069\u308c\u3044\u304c\u8fd1\u3044";
|
||||||
nonSlave.target = slave;
|
|
||||||
nonSlave.thought = "\u305a\u3093\u3061\u3069\u308c\u3044\u304c\u8fd1\u3044";
|
|
||||||
}
|
|
||||||
nonSlave.adjustRelation?.(slave, -dt * 0.08, dt * 0.24, "zunchi_slave_avoid");
|
nonSlave.adjustRelation?.(slave, -dt * 0.08, dt * 0.24, "zunchi_slave_avoid");
|
||||||
slave.adjustRelation?.(nonSlave, -dt * 0.02, 0, "zunchi_slave_avoid");
|
slave.adjustRelation?.(nonSlave, -dt * 0.02, 0, "zunchi_slave_avoid");
|
||||||
if (Math.random() < dt * 0.35) nonSlave.surpriseTimer = Math.max(nonSlave.surpriseTimer || 0, 0.18);
|
if (Math.random() < dt * 0.35) nonSlave.surpriseTimer = Math.max(nonSlave.surpriseTimer || 0, 0.18);
|
||||||
|
|
@ -131,15 +137,32 @@
|
||||||
const familyFightBlocked = this.world.areParentChild ? this.world.areParentChild(this, o) : false;
|
const familyFightBlocked = this.world.areParentChild ? this.world.areParentChild(this, o) : false;
|
||||||
const pairFightBlocked = this.world.areCoParents ? this.world.areCoParents(this, o) : false;
|
const pairFightBlocked = this.world.areCoParents ? this.world.areCoParents(this, o) : false;
|
||||||
if (this.world.canFightPair?.(this, o) && d < 56 && !familyFightBlocked && !pairFightBlocked && !birthPeace && fightRisk && this.fightCooldown <= 0 && o.fightCooldown <= 0 && Math.random() < dt * 0.23 * personalityRisk * fearBrake * friendBrake * conflictBias) {
|
if (this.world.canFightPair?.(this, o) && d < 56 && !familyFightBlocked && !pairFightBlocked && !birthPeace && fightRisk && this.fightCooldown <= 0 && o.fightCooldown <= 0 && Math.random() < dt * 0.23 * personalityRisk * fearBrake * friendBrake * conflictBias) {
|
||||||
this.world.startConflict(this, o);
|
this.conflictTargetId = o.id;
|
||||||
|
o.conflictTargetId = this.id;
|
||||||
|
const urge = 48 + (battleDrug ? 34 : 0) + (mixedZunchiSlave ? 12 : 0);
|
||||||
|
this.conflictUrge = Math.max(this.conflictUrge || 0, urge);
|
||||||
|
o.conflictUrge = Math.max(o.conflictUrge || 0, urge * 0.82);
|
||||||
|
if (typeof applyNeedShock === "function") applyNeedShock(this, { social: dt * urge * 0.32, safety: dt * (battleDrug ? 4 : 1.6) }, o);
|
||||||
|
if (typeof applyNeedShock === "function") applyNeedShock(o, { social: dt * urge * 0.24, safety: dt * (battleDrug ? 4 : 1.6) }, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const compatibleBirthStatus = this.isZunchiSlave === o.isZunchiSlave;
|
const compatibleBirthStatus = this.isZunchiSlave === o.isZunchiSlave;
|
||||||
if (compatibleBirthStatus && !this.sleepDisease && !o.sleepDisease && !this.fightDisease && !o.fightDisease && d < (loveDrug ? 44 : 36) && this.reproductionTimer <= 0 && o.reproductionTimer <= 0 && this.fightTimer <= 0 && o.fightTimer <= 0) {
|
if (compatibleBirthStatus && !this.sleepDisease && !o.sleepDisease && !this.fightDisease && !o.fightDisease && d < (loveDrug ? 52 : 42) && this.reproductionTimer <= 0 && o.reproductionTimer <= 0 && this.fightTimer <= 0 && o.fightTimer <= 0) {
|
||||||
const enough = loveDrug ? (this.hunger < 90 && o.hunger < 92 && this.energy > 18 && o.energy > 18) : (this.hunger < 55 && o.hunger < 60 && this.energy > 44 && o.energy > 44);
|
const enough = loveDrug ? (this.hunger < 90 && o.hunger < 92 && this.energy > 18 && o.energy > 18) : (this.hunger < 55 && o.hunger < 60 && this.energy > 44 && o.energy > 44);
|
||||||
const lonelyBond = loveDrug ? (this.loneliness > 4 || o.loneliness > 4 || this.affection > 0 || o.affection > 0) : (this.loneliness > 24 || o.loneliness > 24);
|
const lonelyBond = loveDrug ? (this.loneliness > 4 || o.loneliness > 4 || this.affection > 0 || o.affection > 0) : (this.loneliness > 24 || o.loneliness > 24);
|
||||||
if (enough && lonelyBond && Math.random() < dt * (loveDrug ? 0.72 : 0.25)) {
|
if (enough && lonelyBond) {
|
||||||
this.world.startBirthRitual(this, o);
|
if (typeof applyNeedShock === "function") {
|
||||||
|
applyNeedShock(this, { social: dt * (loveDrug ? 34 : 14) }, o);
|
||||||
|
applyNeedShock(o, { social: dt * (loveDrug ? 34 : 14) }, this);
|
||||||
|
}
|
||||||
|
if (loveDrug && typeof queueForcedTarinaiBehavior === "function") {
|
||||||
|
queueForcedTarinaiBehavior(this, "approach_mate", { target: o, source: "love_mochi", priority: 92, ttl: 10, reasonText: `${o.name || "相手"}と繁殖したくて近づいている` });
|
||||||
|
queueForcedTarinaiBehavior(o, "approach_mate", { target: this, source: "love_mochi", priority: 92, ttl: 10, reasonText: `${this.name || "相手"}と繁殖したくて近づいている` });
|
||||||
|
}
|
||||||
|
const mateIntent = this.activeBehavior?.id === "approach_mate" || o.activeBehavior?.id === "approach_mate" || this.intent?.actionId === "approach_mate" || o.intent?.actionId === "approach_mate";
|
||||||
|
if (mateIntent && Math.random() < dt * (loveDrug ? 0.78 : 0.30)) {
|
||||||
|
this.world.startBirthRitual(this, o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,6 +274,13 @@
|
||||||
const dx = moveTarget.x - this.x;
|
const dx = moveTarget.x - this.x;
|
||||||
const dy = moveTarget.y - this.y;
|
const dy = moveTarget.y - this.y;
|
||||||
const d = Math.hypot(dx, dy) || 1;
|
const d = Math.hypot(dx, dy) || 1;
|
||||||
|
if (this.state === "wander" && this.target?.detour && d < Math.max(22, (this.radius || 20) * 0.9)) {
|
||||||
|
if (typeof applyNeedSatisfaction === "function") applyNeedSatisfaction(this, { fulfill: 6 }, "wander");
|
||||||
|
this.currentAction = null;
|
||||||
|
this.intentLockTimer = 0;
|
||||||
|
this.goIdle?.("少し歩いた");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.state === "seek_bed" && this.isSleepFurniture(this.target)) {
|
if (this.state === "seek_bed" && this.isSleepFurniture(this.target)) {
|
||||||
const bedDx = this.target.x - this.x;
|
const bedDx = this.target.x - this.x;
|
||||||
const bedDy = this.target.y - this.y;
|
const bedDy = this.target.y - this.y;
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ TEXT_CATALOG.targetLabel = function targetLabelFromCatalog(target) {
|
||||||
TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) {
|
TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) {
|
||||||
if (!t) return "なし";
|
if (!t) return "なし";
|
||||||
if ((t.pinchThoughtTimer || 0) > 0) return "つままれている";
|
if ((t.pinchThoughtTimer || 0) > 0) return "つままれている";
|
||||||
const thought = String(t.thought || "").trim();
|
const behaviorText = typeof activeBehaviorText === "function" ? activeBehaviorText(t) : String(t.activeBehavior?.presentText || t.activeBehavior?.reasonText || "").trim();
|
||||||
if (thought) return thought;
|
if (behaviorText) return behaviorText;
|
||||||
const target = TEXT_CATALOG.targetLabel(t.target);
|
const target = TEXT_CATALOG.targetLabel(t.target);
|
||||||
if (t.state === "eat") return target !== "なし" ? `${target}を食べている` : "食べている";
|
if (t.state === "eat") return target !== "なし" ? `${target}を食べている` : "食べている";
|
||||||
if (t.state === "sleep") return t.target?.type === "nest_box" ? "巣箱の中で体力を回復している" : "体力を回復している";
|
if (t.state === "sleep") return t.target?.type === "nest_box" ? "巣箱の中で体力を回復している" : "体力を回復している";
|
||||||
|
|
@ -116,13 +116,14 @@ TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) {
|
||||||
if (t.state === "intimidate") return target !== "なし" ? `${target}を威嚇している` : "相手を威嚇している";
|
if (t.state === "intimidate") return target !== "なし" ? `${target}を威嚇している` : "相手を威嚇している";
|
||||||
if (t.state === "ant_intimidate") return "アリを威嚇している";
|
if (t.state === "ant_intimidate") return "アリを威嚇している";
|
||||||
if (t.state === "ant_attack") return "アリを攻撃している";
|
if (t.state === "ant_attack") return "アリを攻撃している";
|
||||||
if (t.state === "birth_ritual") return "子が現れる前に体をゆすっている";
|
if (t.state === "birth_ritual") return t.target?.name ? `${t.target.name}と繁殖の前ぶれをしている` : "繁殖の前ぶれをしている";
|
||||||
if (t.state === "sunbath") return "晴れた日なたでストレスを下げている";
|
if (t.state === "sunbath") return "晴れた日なたでストレスを下げている";
|
||||||
if (t.state === "frozen") return "フィールド外で冷凍保存されている";
|
if (t.state === "frozen") return "フィールド外で冷凍保存されている";
|
||||||
if (t.state === "seek_food") return target !== "なし" ? `${target}を探している` : "お腹がすいている";
|
if (t.state === "seek_food") return target !== "なし" ? `${target}を探している` : "お腹がすいている";
|
||||||
if (t.state === "seek_water") return t.explosionDisease ? "爆発病で水を探している" : "ずんち病で水を探している";
|
if (t.state === "seek_water") return t.explosionDisease ? "爆発病で水を探している" : "ずんち病で水を探している";
|
||||||
if (t.state === "seek_bed") return target !== "なし" ? `${target}へ向かっている` : "眠る場所を探している";
|
if (t.state === "seek_bed") return target !== "なし" ? `${target}へ向かっている` : "眠る場所を探している";
|
||||||
if (t.state === "play_ball") return "ボールが気になっている";
|
if (t.state === "play_ball") return "ボールが気になっている";
|
||||||
|
if (t.state === "seek_friend" && t.intent?.actionId === "approach_mate") return t.target?.name ? `${t.target.name}に繁殖のため近づいている` : "繁殖できる相手を探している";
|
||||||
if (t.state === "seek_friend") return "仲間を探している";
|
if (t.state === "seek_friend") return "仲間を探している";
|
||||||
if (t.state === "follow_parent") return "親についていく";
|
if (t.state === "follow_parent") return "親についていく";
|
||||||
if (t.state === "cursor_friend") return "カーソルについていく";
|
if (t.state === "cursor_friend") return "カーソルについていく";
|
||||||
|
|
@ -133,6 +134,8 @@ TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) {
|
||||||
if ((t.hurtTimer || 0) > 0.08) return "痛みで混乱している";
|
if ((t.hurtTimer || 0) > 0.08) return "痛みで混乱している";
|
||||||
if ((t.fearTimer || 0) > 0.08) return "怖がっている";
|
if ((t.fearTimer || 0) > 0.08) return "怖がっている";
|
||||||
if ((t.grassEatTimer || 0) > 0.04) return "草を食べている";
|
if ((t.grassEatTimer || 0) > 0.04) return "草を食べている";
|
||||||
|
const thought = String(t.thought || "").trim();
|
||||||
|
if (thought) return thought;
|
||||||
return "周囲を見ている";
|
return "周囲を見ている";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@ function bindUI() {
|
||||||
window.TarinaiSaveSystem?.bindSaveSystem?.();
|
window.TarinaiSaveSystem?.bindSaveSystem?.();
|
||||||
window.TarinaiFreezeSystem?.bindFreezeSystem?.();
|
window.TarinaiFreezeSystem?.bindFreezeSystem?.();
|
||||||
uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []);
|
uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []);
|
||||||
|
function isActivePlacementTool() {
|
||||||
|
const def = typeof toolDefinition === "function" ? toolDefinition(world.tool) : null;
|
||||||
|
return Boolean(def?.placeable && typeof toolItemType === "function" && toolItemType(world.tool));
|
||||||
|
}
|
||||||
|
function clickedInsideGameOrToolUi(target) {
|
||||||
|
return Boolean((canvas && canvas.contains?.(target)) || ui.toolPalette?.contains?.(target));
|
||||||
|
}
|
||||||
syncToolSizeBadges();
|
syncToolSizeBadges();
|
||||||
for (const toggle of document.querySelectorAll(".panel-card-toggle")) {
|
for (const toggle of document.querySelectorAll(".panel-card-toggle")) {
|
||||||
toggle.addEventListener("click", () => {
|
toggle.addEventListener("click", () => {
|
||||||
|
|
@ -179,6 +186,11 @@ function bindUI() {
|
||||||
if (!ui.soundMenu || ui.soundMenu.contains(e.target)) return;
|
if (!ui.soundMenu || ui.soundMenu.contains(e.target)) return;
|
||||||
closeSoundPanel();
|
closeSoundPanel();
|
||||||
});
|
});
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (!isActivePlacementTool()) return;
|
||||||
|
if (clickedInsideGameOrToolUi(e.target)) return;
|
||||||
|
selectTool("observe"); // 道具設置モードを解除
|
||||||
|
});
|
||||||
ui.creditsBtn?.addEventListener("click", () => { audio.uiClick?.(); openCreditsDialog(); });
|
ui.creditsBtn?.addEventListener("click", () => { audio.uiClick?.(); openCreditsDialog(); });
|
||||||
ui.creditsCloseBtn?.addEventListener("click", closeCreditsDialog);
|
ui.creditsCloseBtn?.addEventListener("click", closeCreditsDialog);
|
||||||
ui.creditsDialog?.addEventListener("click", (e) => {
|
ui.creditsDialog?.addEventListener("click", (e) => {
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,29 @@ function selectedInfoRowHtml(label, valueHtml, metric = "") {
|
||||||
return `<div class="info-row"${metricAttr}><span>${escapeHtml(label)}</span><strong>${valueHtml}</strong></div>`;
|
return `<div class="info-row"${metricAttr}><span>${escapeHtml(label)}</span><strong>${valueHtml}</strong></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function selectedInventoryItems(t) {
|
||||||
|
const worldRef = t?.world || world;
|
||||||
|
const labels = { grass_bed: "かんたんベッド", plushie: "ぬいぐるみ" };
|
||||||
|
const items = (worldRef?.items || []).filter(it => it && !it.dead && it.isStructure && it.ownerId === t?.id);
|
||||||
|
const counts = new Map();
|
||||||
|
for (const it of items) {
|
||||||
|
const label = labels[it.type] || toolLabel(it.type) || it.type;
|
||||||
|
counts.set(label, (counts.get(label) || 0) + 1);
|
||||||
|
}
|
||||||
|
return Array.from(counts.entries()).map(([label, count]) => count > 1 ? `${label} ×${count}` : label);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedInventoryHtml(t) {
|
||||||
|
const entries = selectedInventoryItems(t);
|
||||||
|
if (!entries.length) return `<div class="info-row"><span>所持品</span><strong>なし</strong></div>`;
|
||||||
|
return `<div class="info-row"><span>所持品</span><strong>${entries.map(v => `<span class="personality-tag">${escapeHtml(v)}</span>`).join("")}</strong></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectedStateText(t) {
|
||||||
|
return stateLabel(t?.state);
|
||||||
|
}
|
||||||
|
|
||||||
function renderSelected() {
|
function renderSelected() {
|
||||||
const t = world.selected;
|
const t = world.selected;
|
||||||
if (!t || t.dead || !world.tarinai.includes(t)) {
|
if (!t || t.dead || !world.tarinai.includes(t)) {
|
||||||
|
|
@ -195,7 +218,7 @@ function renderSelected() {
|
||||||
const needKeys = typeof TARINAI_NEED_KEYS !== "undefined" ? TARINAI_NEED_KEYS : ["food", "sleep", "health", "safety", "social", "fulfill"];
|
const needKeys = typeof TARINAI_NEED_KEYS !== "undefined" ? TARINAI_NEED_KEYS : ["food", "sleep", "health", "safety", "social", "fulfill"];
|
||||||
const snapshot = [
|
const snapshot = [
|
||||||
t.id, t.type, t.name, generationDisplay, t.state, t.thought, targetLabel(t.target),
|
t.id, t.type, t.name, generationDisplay, t.state, t.thought, targetLabel(t.target),
|
||||||
t.intent?.need || "", t.intent?.actionId || "", t.intent?.actionLabel || "", t.intent?.reasonText || "", needKeys.map(key => `${key}:${t.needs?.[key] ?? 0}`).join(","),
|
t.intent?.need || "", t.intent?.actionId || "", t.intent?.actionLabel || "", t.intent?.reasonText || "", t.activeBehavior?.id || "", t.activeBehavior?.presentText || "", needKeys.map(key => `${key}:${t.needs?.[key] ?? 0}`).join(","),
|
||||||
fmt(t.age), fmt(t.lifeSpan), fmt(t.hunger), fmt(t.loneliness),
|
fmt(t.age), fmt(t.lifeSpan), fmt(t.hunger), fmt(t.loneliness),
|
||||||
fmt(t.energy), fmt(t.stress), fmt(t.mood),
|
fmt(t.energy), fmt(t.stress), fmt(t.mood),
|
||||||
t.parentNames?.join("+") || "", t.children?.length || 0, fmt(t.lack),
|
t.parentNames?.join("+") || "", t.children?.length || 0, fmt(t.lack),
|
||||||
|
|
@ -206,7 +229,7 @@ function renderSelected() {
|
||||||
personalitySnapshot, personalityDailySnapshot(t),
|
personalitySnapshot, personalityDailySnapshot(t),
|
||||||
["life", "attack", "speed", "size"].map(key => geneticPercentText(t, key)).join(","), fmt((t.growth || 0) * 100),
|
["life", "attack", "speed", "size"].map(key => geneticPercentText(t, key)).join(","), fmt((t.growth || 0) * 100),
|
||||||
itemEffectSummary.map(e => `${e.id}:${e.detail}`).join(","),
|
itemEffectSummary.map(e => `${e.id}:${e.detail}`).join(","),
|
||||||
(t.recentChangeCauses || []).slice(0, 8).map(c => `${Math.round(c.time || 0)}:${c.reason || ""}:${c.target || ""}:${c.value ?? ""}`).join("~"),
|
selectedInventoryItems(t).join(","),
|
||||||
(t.records || []).slice(0, 6).map(r => `${Math.round(r.time || 0)}:${r.kind || ""}:${r.text || ""}`).join("~")
|
(t.records || []).slice(0, 6).map(r => `${Math.round(r.time || 0)}:${r.kind || ""}:${r.text || ""}`).join("~")
|
||||||
].join("|");
|
].join("|");
|
||||||
if (uiCache.selectedSnapshot === snapshot) return;
|
if (uiCache.selectedSnapshot === snapshot) return;
|
||||||
|
|
@ -237,12 +260,9 @@ function renderSelected() {
|
||||||
const recordText = quoteNameInRecordText(r.text || "", t.name);
|
const recordText = quoteNameInRecordText(r.text || "", t.name);
|
||||||
return `<li><span>${escapeHtml(timeText)}</span><strong>${escapeHtml(recordText)}</strong></li>`;
|
return `<li><span>${escapeHtml(timeText)}</span><strong>${escapeHtml(recordText)}</strong></li>`;
|
||||||
}).join("") || `<li><span>-</span><strong>\u8a18\u9332\u306a\u3057</strong></li>`;
|
}).join("") || `<li><span>-</span><strong>\u8a18\u9332\u306a\u3057</strong></li>`;
|
||||||
const recentCauseHtml = recentChangeCausesHtml(t);
|
|
||||||
const basicHtml = `
|
const basicHtml = `
|
||||||
<div class="info-row"><span>\u4e16\u4ee3</span><strong>${escapeHtml(generationDisplay || "\u4e0d\u660e")}</strong></div>
|
<div class="info-row"><span>\u4e16\u4ee3</span><strong>${escapeHtml(generationDisplay || "\u4e0d\u660e")}</strong></div>
|
||||||
<div class="info-row"><span>\u72b6\u614b</span><strong>${escapeHtml(stateLabel(t.state))}</strong></div>
|
<div class="info-row"><span>\u72b6\u614b</span><strong>${escapeHtml(selectedStateText(t))}</strong></div>
|
||||||
<div class="info-row"><span>\u3044\u307e</span><strong>${escapeHtml(t.intent?.actionLabel || stateLabel(t.state))}</strong></div>
|
|
||||||
<div class="info-row"><span>\u7406\u7531</span><strong>${escapeHtml(reasonLabel(t))}</strong></div>
|
|
||||||
<div class="info-row"><span>\u5bfe\u8c61</span><strong>${escapeHtml(targetLabel(t.target))}</strong></div>
|
<div class="info-row"><span>\u5bfe\u8c61</span><strong>${escapeHtml(targetLabel(t.target))}</strong></div>
|
||||||
<div class="info-row"><span>\u5e74\u9f62</span><strong>${fmt(t.age)} / ${fmt(t.lifeSpan)}</strong></div>
|
<div class="info-row"><span>\u5e74\u9f62</span><strong>${fmt(t.age)} / ${fmt(t.lifeSpan)}</strong></div>
|
||||||
<div class="info-row"><span>\u6700\u5927\u5bff\u547d</span><strong>${geneticPercentText(t, "life")}</strong></div>
|
<div class="info-row"><span>\u6700\u5927\u5bff\u547d</span><strong>${geneticPercentText(t, "life")}</strong></div>
|
||||||
|
|
@ -254,10 +274,11 @@ function renderSelected() {
|
||||||
const label = (typeof TARINAI_NEED_LABELS !== "undefined" && TARINAI_NEED_LABELS[key]) || key;
|
const label = (typeof TARINAI_NEED_LABELS !== "undefined" && TARINAI_NEED_LABELS[key]) || key;
|
||||||
const value = typeof getNeedDisplayValue === "function" ? getNeedDisplayValue(t.needs?.[key] || 0) : Math.round((t.needs?.[key] || 0) / 10);
|
const value = typeof getNeedDisplayValue === "function" ? getNeedDisplayValue(t.needs?.[key] || 0) : Math.round((t.needs?.[key] || 0) / 10);
|
||||||
const chosen = t.intent?.need === key ? ` data-chosen-need="true"` : "";
|
const chosen = t.intent?.need === key ? ` data-chosen-need="true"` : "";
|
||||||
return `<div class="info-row need-row"${chosen}><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></div>`;
|
return `<div class="need-cell"${chosen}><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
const healthHtml = [
|
const healthHtml = [
|
||||||
needRowsHtml,
|
`<div class="info-row action-reason-row"><span>\u884c\u52d5</span><strong>${escapeHtml(reasonLabel(t))}</strong></div>`,
|
||||||
|
`<div class="need-grid">${needRowsHtml}</div>`,
|
||||||
selectedInfoRowHtml("\u4f53\u529b", fmt(t.energy), "energy"),
|
selectedInfoRowHtml("\u4f53\u529b", fmt(t.energy), "energy"),
|
||||||
selectedInfoRowHtml("\u30b9\u30c8\u30ec\u30b9", fmt(t.stress), "stress"),
|
selectedInfoRowHtml("\u30b9\u30c8\u30ec\u30b9", fmt(t.stress), "stress"),
|
||||||
].join("");
|
].join("");
|
||||||
|
|
@ -280,12 +301,13 @@ function renderSelected() {
|
||||||
${effectBadges.length ? `<div class="selected-effects">${effectBadges.join("")}</div>` : ""}
|
${effectBadges.length ? `<div class="selected-effects">${effectBadges.join("")}</div>` : ""}
|
||||||
<div class="selected-data-groups">
|
<div class="selected-data-groups">
|
||||||
${selectedDataCategoryHtml("basic", "\u57fa\u672c", basicHtml)}
|
${selectedDataCategoryHtml("basic", "\u57fa\u672c", basicHtml)}
|
||||||
${selectedDataCategoryHtml("health", "\u4f53\u8abf", healthHtml)}
|
${selectedDataCategoryHtml("health", "\u6b32\u6c42", healthHtml)}
|
||||||
${selectedDataCategoryHtml("effects", "\u30a2\u30a4\u30c6\u30e0\u52b9\u679c", itemEffectHtml)}
|
${selectedDataCategoryHtml("effects", "\u30a2\u30a4\u30c6\u30e0\u52b9\u679c", itemEffectHtml)}
|
||||||
${selectedDataCategoryHtml("personality", "\u6027\u683c", personalityHtml)}
|
${selectedDataCategoryHtml("personality", "\u6027\u683c", personalityHtml)}
|
||||||
${selectedDataCategoryHtml("relation", "\u95a2\u4fc2", relationHtml)}
|
${selectedDataCategoryHtml("relation", "\u95a2\u4fc2", relationHtml)}
|
||||||
${selectedDataCategoryHtml("family", "\u5bb6\u65cf", familyHtml)}
|
${selectedDataCategoryHtml("family", "\u5bb6\u65cf", familyHtml)}
|
||||||
${selectedDataCategoryHtml("records", "\u8a18\u9332", `<div class="selected-records"><h3>\u6700\u8fd1\u306e\u5909\u5316\u539f\u56e0</h3><ol class="selected-change-list">${recentCauseHtml}</ol><h3>\u500b\u4f53\u5225\u306e\u8a18\u9332</h3><ol>${recordHtml}</ol></div>`)}
|
${selectedDataCategoryHtml("inventory", "\u6240\u6301\u54c1", selectedInventoryHtml(t))}
|
||||||
|
${selectedDataCategoryHtml("records", "\u8a18\u9332", `<div class="selected-records"><ol>${recordHtml}</ol></div>`)}
|
||||||
</div>
|
</div>
|
||||||
<div class="selected-actions"><button type="button" class="favorite-toggle ${t.favorite ? "active" : ""}" data-selected-action="favorite">${t.favorite ? "\u2605 \u304a\u6c17\u306b\u5165\u308a\u89e3\u9664" : "\u2606 \u304a\u6c17\u306b\u5165\u308a"}</button></div>
|
<div class="selected-actions"><button type="button" class="favorite-toggle ${t.favorite ? "active" : ""}" data-selected-action="favorite">${t.favorite ? "\u2605 \u304a\u6c17\u306b\u5165\u308a\u89e3\u9664" : "\u2606 \u304a\u6c17\u306b\u5165\u308a"}</button></div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -303,8 +325,15 @@ function stateLabel(s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reasonLabel(t) {
|
function reasonLabel(t) {
|
||||||
|
if (!t) return "なし";
|
||||||
|
const behavior = typeof activeBehaviorText === "function" ? activeBehaviorText(t) : String(t.activeBehavior?.presentText || t.activeBehavior?.reasonText || "").trim();
|
||||||
|
if (behavior) return behavior;
|
||||||
|
const state = String(t.state || "");
|
||||||
|
const live = window.TEXT_CATALOG?.reasonLabel?.(t) || "";
|
||||||
|
const liveStates = new Set(["birth_ritual", "eat", "sleep", "panic", "fight", "intimidate", "ant_attack", "ant_intimidate", "frozen"]);
|
||||||
|
if (live && (liveStates.has(state) || (t.birthRitualTimer || 0) > 0.04 || (t.eatTimer || 0) > 0.04 || (t.fightTimer || 0) > 0.04 || (t.intimidateTimer || 0) > 0.04)) return live;
|
||||||
if (t?.intent?.reasonText) return t.intent.reasonText;
|
if (t?.intent?.reasonText) return t.intent.reasonText;
|
||||||
return window.TEXT_CATALOG?.reasonLabel?.(t) || "なし";
|
return live || "なし";
|
||||||
}
|
}
|
||||||
|
|
||||||
function targetLabel(target) {
|
function targetLabel(target) {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ function renderToolPalette() {
|
||||||
btn.className = "tool";
|
btn.className = "tool";
|
||||||
btn.dataset.tool = toolId;
|
btn.dataset.tool = toolId;
|
||||||
btn.textContent = def.label || toolId;
|
btn.textContent = def.label || toolId;
|
||||||
|
attachToolSpritePreview(btn, def, toolId);
|
||||||
if (toolId === world?.tool) btn.classList.add("selected");
|
if (toolId === world?.tool) btn.classList.add("selected");
|
||||||
body.appendChild(btn);
|
body.appendChild(btn);
|
||||||
}
|
}
|
||||||
|
|
@ -37,6 +38,33 @@ function renderToolPalette() {
|
||||||
ui.toolPalette.appendChild(frag);
|
ui.toolPalette.appendChild(frag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attachToolSpritePreview(btn, def, toolId) {
|
||||||
|
const itemType = def?.itemType || toolId;
|
||||||
|
if (!btn || !itemType || !def?.placeable) return;
|
||||||
|
const drawPreview = window.drawToolItemPreview || window.drawConsumableFieldSprite;
|
||||||
|
if (typeof drawPreview !== "function") return;
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.className = "tool-sprite-preview";
|
||||||
|
canvas.width = 72;
|
||||||
|
canvas.height = 72;
|
||||||
|
canvas.setAttribute("aria-hidden", "true");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return;
|
||||||
|
let drawn = false;
|
||||||
|
if (typeof window.drawToolItemPreview === "function") {
|
||||||
|
drawn = window.drawToolItemPreview(ctx, itemType, { width: canvas.width, height: canvas.height, compact: true }) !== false;
|
||||||
|
} else if (typeof window.isConsumableSpriteType === "function" && window.isConsumableSpriteType(itemType)) {
|
||||||
|
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||||
|
const scale = itemType === "protein" || itemType === "niteropu" ? 0.82 : 0.92;
|
||||||
|
ctx.scale(scale, scale);
|
||||||
|
window.drawConsumableFieldSprite(ctx, itemType, 13.5, 112.4, { compact: true });
|
||||||
|
drawn = true;
|
||||||
|
}
|
||||||
|
if (!drawn) return;
|
||||||
|
btn.classList.add("tool-has-canvas-icon");
|
||||||
|
btn.prepend(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
function toolSizeFor(tool) {
|
function toolSizeFor(tool) {
|
||||||
if (!SCALABLE_TOOLS.has(tool)) return "";
|
if (!SCALABLE_TOOLS.has(tool)) return "";
|
||||||
return typeof normalizedToolSizeFor === "function" ? normalizedToolSizeFor(world, tool) : ((world.toolSizes && world.toolSizes[tool]) || world.toolSize || "medium");
|
return typeof normalizedToolSizeFor === "function" ? normalizedToolSizeFor(world, tool) : ((world.toolSizes && world.toolSizes[tool]) || world.toolSize || "medium");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
const APP_VERSION = "15.24.2";
|
const APP_VERSION = "15.24.14";
|
||||||
const APP_BUILD = "needs-compliance-fix";
|
const APP_BUILD = "forced-behavior-sleep-rhythm";
|
||||||
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
|
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
|
||||||
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;
|
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,7 @@
|
||||||
const radius = rand(minR, maxR);
|
const radius = rand(minR, maxR);
|
||||||
const x = clamp(source.x + Math.cos(angle) * radius, 44, this.w - 44);
|
const x = clamp(source.x + Math.cos(angle) * radius, 44, this.w - 44);
|
||||||
const y = clamp(source.y + Math.sin(angle) * radius * 0.72, 44, this.h - 44);
|
const y = clamp(source.y + Math.sin(angle) * radius * 0.72, 44, this.h - 44);
|
||||||
if (this.grassSpotOpen(x, y, { minGrassGap: 30, avoidTarinai: true })) return { x, y };
|
if (this.grassSpotOpen(x, y, { minGrassGap: fertile ? 24 : 22, avoidTarinai: true })) return { x, y };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,6 @@
|
||||||
}
|
}
|
||||||
for (let i = 0; i < grassCount; i++) this.items.push(new Item("grass", rand(70, this.w - 70), rand(70, this.h - 70)));
|
for (let i = 0; i < grassCount; i++) this.items.push(new Item("grass", rand(70, this.w - 70), rand(70, this.h - 70)));
|
||||||
this.items.push(new Item("stone", this.w * 0.68, this.h * 0.32));
|
this.items.push(new Item("stone", this.w * 0.68, this.h * 0.32));
|
||||||
this.items.push(new Item("bed", this.w * 0.82, this.h * 0.22));
|
|
||||||
this.items.push(new Item("bed", this.w * 0.18, this.h * 0.78));
|
|
||||||
this.updateItemCounts();
|
this.updateItemCounts();
|
||||||
this.updateEffectCounts();
|
this.updateEffectCounts();
|
||||||
this.markTerrainDirty?.("reset");
|
this.markTerrainDirty?.("reset");
|
||||||
|
|
@ -430,8 +428,12 @@
|
||||||
b.birthRitualRole = 1;
|
b.birthRitualRole = 1;
|
||||||
a.birthRitualLeader = true;
|
a.birthRitualLeader = true;
|
||||||
b.birthRitualLeader = false;
|
b.birthRitualLeader = false;
|
||||||
a.setActionState("birth_ritual", { target: b, reason: "近くで体をゆすっている" });
|
a.setActionState("birth_ritual", { target: b, reason: "繁殖の前ぶれをしている" });
|
||||||
b.setActionState("birth_ritual", { target: a, reason: "近くで体をゆすっている" });
|
b.setActionState("birth_ritual", { target: a, reason: "繁殖の前ぶれをしている" });
|
||||||
|
if (typeof setLiveActionText === "function") {
|
||||||
|
setLiveActionText(a, { need: "social", actionId: "birth_ritual", actionLabel: "繁殖の前ぶれをしている", reasonText: `${b.name || "相手"}と繁殖の前ぶれをしている`, target: b });
|
||||||
|
setLiveActionText(b, { need: "social", actionId: "birth_ritual", actionLabel: "繁殖の前ぶれをしている", reasonText: `${a.name || "相手"}と繁殖の前ぶれをしている`, target: a });
|
||||||
|
}
|
||||||
a.reproductionTimer = CONFIG.reproductionCooldown + rand(2, 10);
|
a.reproductionTimer = CONFIG.reproductionCooldown + rand(2, 10);
|
||||||
b.reproductionTimer = CONFIG.reproductionCooldown + rand(2, 10);
|
b.reproductionTimer = CONFIG.reproductionCooldown + rand(2, 10);
|
||||||
a.surpriseTimer = Math.max(a.surpriseTimer, 0.35);
|
a.surpriseTimer = Math.max(a.surpriseTimer, 0.35);
|
||||||
|
|
@ -496,7 +498,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
canStartFight(t) {
|
canStartFight(t) {
|
||||||
if (!t || t.dead) return false;
|
if (!t || t.dead || typeof t.relationTo !== "function") return false;
|
||||||
if (t.sleepDisease) return false;
|
if (t.sleepDisease) return false;
|
||||||
// Disease fight rule: explosion, zunchi, and fight disease can lash out;
|
// Disease fight rule: explosion, zunchi, and fight disease can lash out;
|
||||||
// sleep disease cannot fight or be challenged, and other diseases cannot start fights.
|
// sleep disease cannot fight or be challenged, and other diseases cannot start fights.
|
||||||
|
|
@ -508,7 +510,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
canBeFightTarget(t) {
|
canBeFightTarget(t) {
|
||||||
return Boolean(t && !t.dead && !t.sleepDisease);
|
return Boolean(t && !t.dead && typeof t.relationTo === "function" && !t.sleepDisease);
|
||||||
},
|
},
|
||||||
|
|
||||||
canFightPair(actor, target) {
|
canFightPair(actor, target) {
|
||||||
|
|
@ -540,7 +542,7 @@
|
||||||
loneliness: rand(30, 56),
|
loneliness: rand(30, 56),
|
||||||
energy: rand(55, 95),
|
energy: rand(55, 95),
|
||||||
stress: rand(0, 14),
|
stress: rand(0, 14),
|
||||||
need: Math.random() < 0.6 ? pick([a.need, b.need]) : pick(NEEDS),
|
need: "",
|
||||||
generation: Math.max(a.generation, b.generation) + 1,
|
generation: Math.max(a.generation, b.generation) + 1,
|
||||||
lifeSpan: rand(1500, 2200) * (childGenetics.lifeSpanMul || 1),
|
lifeSpan: rand(1500, 2200) * (childGenetics.lifeSpanMul || 1),
|
||||||
parents: [this.tarinaiFamilyKey(a), this.tarinaiFamilyKey(b)],
|
parents: [this.tarinaiFamilyKey(a), this.tarinaiFamilyKey(b)],
|
||||||
|
|
@ -628,14 +630,15 @@
|
||||||
|
|
||||||
startConflict(a, b) {
|
startConflict(a, b) {
|
||||||
if (!a || !b || a.dead || b.dead) return;
|
if (!a || !b || a.dead || b.dead) return;
|
||||||
|
if (typeof a.relationTo !== "function" || typeof b.relationTo !== "function") return;
|
||||||
if ((a.fightMochiTimer || 0) > 0.04 || (b.fightMochiTimer || 0) > 0.04) { this.startForcedFight(a, b); return; }
|
if ((a.fightMochiTimer || 0) > 0.04 || (b.fightMochiTimer || 0) > 0.04) { this.startForcedFight(a, b); return; }
|
||||||
if (this.areParentChild(a, b) || this.areCoParents?.(a, b)) return;
|
if (this.areParentChild(a, b) || this.areCoParents?.(a, b)) return;
|
||||||
if ((a.postBirthPeaceTimer || 0) > 0 || (b.postBirthPeaceTimer || 0) > 0) return;
|
if ((a.postBirthPeaceTimer || 0) > 0 || (b.postBirthPeaceTimer || 0) > 0) return;
|
||||||
if (a.fightTimer > 0.04 || b.fightTimer > 0.04 || a.intimidateTimer > 0.04 || b.intimidateTimer > 0.04) return;
|
if (a.fightTimer > 0.04 || b.fightTimer > 0.04 || a.intimidateTimer > 0.04 || b.intimidateTimer > 0.04) return;
|
||||||
const aAggressiveIntent = a.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
|
const aAggressiveIntent = a.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
|
||||||
const bAggressiveIntent = b.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
|
const bAggressiveIntent = b.shouldApplyPersonalityBehavior?.("aggression", 1) ? 1 : 0;
|
||||||
const scoreA = a.energy * 0.38 + a.mood * 0.18 + a.stress * 0.12 + (a.type === "angry" ? 12 : 0) + aAggressiveIntent * 8 + ((b.relationTo(a.id).fear || 0) * 0.65) + (a.isZunchiSlave ? -10 : 0) + ((a.fightMochiTimer || 0) > 0.04 ? 9 : 0) + rand(-5, 5);
|
const scoreA = a.energy * 0.38 + a.mood * 0.18 + a.stress * 0.12 + (a.type === "angry" ? 12 : 0) + aAggressiveIntent * 8 + ((typeof b.relationTo === "function" ? (b.relationTo(a.id).fear || 0) : 0) * 0.65) + (a.isZunchiSlave ? -10 : 0) + ((a.fightMochiTimer || 0) > 0.04 ? 9 : 0) + rand(-5, 5);
|
||||||
const scoreB = b.energy * 0.38 + b.mood * 0.18 + b.stress * 0.12 + (b.type === "angry" ? 12 : 0) + bAggressiveIntent * 8 + ((a.relationTo(b.id).fear || 0) * 0.65) + (b.isZunchiSlave ? -10 : 0) + ((b.fightMochiTimer || 0) > 0.04 ? 9 : 0) + rand(-5, 5);
|
const scoreB = b.energy * 0.38 + b.mood * 0.18 + b.stress * 0.12 + (b.type === "angry" ? 12 : 0) + bAggressiveIntent * 8 + ((typeof a.relationTo === "function" ? (a.relationTo(b.id).fear || 0) : 0) * 0.65) + (b.isZunchiSlave ? -10 : 0) + ((b.fightMochiTimer || 0) > 0.04 ? 9 : 0) + rand(-5, 5);
|
||||||
const actor = scoreA >= scoreB ? a : b;
|
const actor = scoreA >= scoreB ? a : b;
|
||||||
const target = actor === a ? b : a;
|
const target = actor === a ? b : a;
|
||||||
if (!this.canFightPair(actor, target)) return;
|
if (!this.canFightPair(actor, target)) return;
|
||||||
|
|
@ -654,7 +657,7 @@
|
||||||
startIntimidation(actor, target, actorScore = 0, targetScore = 0) {
|
startIntimidation(actor, target, actorScore = 0, targetScore = 0) {
|
||||||
if (!actor || !target || actor.dead || target.dead) return false;
|
if (!actor || !target || actor.dead || target.dead) return false;
|
||||||
if (actor.lowHealthSprite && actor.lowHealthSprite()) return false;
|
if (actor.lowHealthSprite && actor.lowHealthSprite()) return false;
|
||||||
const fearLoad = (target.relationTo(actor.id).fear || 0) * 0.72 + target.personalityProfile().fear * 8;
|
const fearLoad = ((typeof target.relationTo === "function" ? (target.relationTo(actor.id).fear || 0) : 0) * 0.72) + target.personalityProfile().fear * 8;
|
||||||
const actorAggression = Math.max(0, actor.currentPersonality?.aggression || 0);
|
const actorAggression = Math.max(0, actor.currentPersonality?.aggression || 0);
|
||||||
const targetAggression = Math.max(0, target.currentPersonality?.aggression || 0);
|
const targetAggression = Math.max(0, target.currentPersonality?.aggression || 0);
|
||||||
const actorSize = actor.effectiveScale ? actor.effectiveScale() : (actor.scale || 0.28);
|
const actorSize = actor.effectiveScale ? actor.effectiveScale() : (actor.scale || 0.28);
|
||||||
|
|
@ -670,6 +673,7 @@
|
||||||
actor.intimidateTimer = Math.max(actor.intimidateTimer, rand(2.45, 3.35));
|
actor.intimidateTimer = Math.max(actor.intimidateTimer, rand(2.45, 3.35));
|
||||||
actor.intimidateTargetId = target.id;
|
actor.intimidateTargetId = target.id;
|
||||||
actor.setActionState("intimidate", { target, reason: "相手を威嚇している" });
|
actor.setActionState("intimidate", { target, reason: "相手を威嚇している" });
|
||||||
|
if (typeof setLiveActionText === "function") setLiveActionText(actor, { need: "social", subNeed: "conflict", actionId: "intimidate_enemy", actionLabel: "威嚇している", reasonText: `${target.name || "相手"}を威嚇している`, target, phase: "perform", source: "behavior" });
|
||||||
this.effects.push(new Effect("ring", actor.x, actor.y - actor.radius * 0.65, {
|
this.effects.push(new Effect("ring", actor.x, actor.y - actor.radius * 0.65, {
|
||||||
life: 0.42,
|
life: 0.42,
|
||||||
size: actor.radius * 0.55,
|
size: actor.radius * 0.55,
|
||||||
|
|
@ -716,6 +720,10 @@
|
||||||
if ((a.postBirthPeaceTimer || 0) > 0 || (b.postBirthPeaceTimer || 0) > 0) return;
|
if ((a.postBirthPeaceTimer || 0) > 0 || (b.postBirthPeaceTimer || 0) > 0) return;
|
||||||
a.setActionState("fight", { target: b, reason: `${b.name || "相手"}と喧嘩している` });
|
a.setActionState("fight", { target: b, reason: `${b.name || "相手"}と喧嘩している` });
|
||||||
b.setActionState("fight", { target: a, reason: `${a.name || "相手"}と喧嘩している` });
|
b.setActionState("fight", { target: a, reason: `${a.name || "相手"}と喧嘩している` });
|
||||||
|
if (typeof setLiveActionText === "function") {
|
||||||
|
setLiveActionText(a, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: "喧嘩している", reasonText: `${b.name || "相手"}と喧嘩している`, target: b, phase: "perform", source: "behavior" });
|
||||||
|
setLiveActionText(b, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: "喧嘩している", reasonText: `${a.name || "相手"}と喧嘩している`, target: a, phase: "perform", source: "behavior" });
|
||||||
|
}
|
||||||
a.fightTimer = Math.max(a.fightTimer, rand(4.5, 7.0));
|
a.fightTimer = Math.max(a.fightTimer, rand(4.5, 7.0));
|
||||||
b.fightTimer = Math.max(b.fightTimer, rand(4.5, 7.0));
|
b.fightTimer = Math.max(b.fightTimer, rand(4.5, 7.0));
|
||||||
a.nextHeadbutt = this.time + rand(0.08, 0.18);
|
a.nextHeadbutt = this.time + rand(0.08, 0.18);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,136 @@
|
||||||
(function (global) {
|
(function (global) {
|
||||||
const World = global.World;
|
const World = global.World;
|
||||||
if (!World) throw new Error("World is not available for mixin: world_placement_log.js");
|
if (!World) throw new Error("World is not available for mixin: world_placement_log.js");
|
||||||
|
|
||||||
|
const SIGNBOARD_CHARS_PER_LINE = 6;
|
||||||
|
const SIGNBOARD_LINES = 3;
|
||||||
|
const SIGNBOARD_TEXT_LIMIT = SIGNBOARD_CHARS_PER_LINE * SIGNBOARD_LINES;
|
||||||
|
|
||||||
|
function ensureSignboardEditor() {
|
||||||
|
let dialog = document.getElementById("signboardEditor");
|
||||||
|
if (dialog) return dialog;
|
||||||
|
dialog = document.createElement("div");
|
||||||
|
dialog.id = "signboardEditor";
|
||||||
|
dialog.className = "signboard-editor hidden";
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<div class="signboard-editor-panel" role="dialog" aria-modal="true" aria-labelledby="signboardEditorTitle">
|
||||||
|
<div class="signboard-editor-titlebar">
|
||||||
|
<h2 id="signboardEditorTitle">\u770b\u677f</h2>
|
||||||
|
<button id="signboardEditorClose" class="signboard-editor-close" type="button" aria-label="\u9589\u3058\u308b">×</button>
|
||||||
|
</div>
|
||||||
|
<textarea id="signboardEditorText" maxlength="${SIGNBOARD_TEXT_LIMIT}" rows="3" spellcheck="false"></textarea>
|
||||||
|
<div class="signboard-editor-meta"><span>6文字×3行まで</span><span id="signboardEditorCount">0/${SIGNBOARD_TEXT_LIMIT}</span></div>
|
||||||
|
<div class="signboard-editor-actions">
|
||||||
|
<button id="signboardEditorClear" class="btn" type="button">\u6d88\u3059</button>
|
||||||
|
<button id="signboardEditorCancel" class="btn" type="button">\u30ad\u30e3\u30f3\u30bb\u30eb</button>
|
||||||
|
<button id="signboardEditorApply" class="btn primary" type="button">\u66f8\u304d\u8fbc\u3080</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
document.body.appendChild(dialog);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sliceGraphemes(text, limit) {
|
||||||
|
const chars = Array.from(String(text || ""));
|
||||||
|
return chars.slice(0, Math.max(0, limit)).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeSignboardText(value = "") {
|
||||||
|
const normalized = String(value || "").replace(/\r/g, "").replace(/[\t ]+/g, " ");
|
||||||
|
const rawLines = normalized.split("\n");
|
||||||
|
const out = [];
|
||||||
|
for (const rawLine of rawLines) {
|
||||||
|
let rest = String(rawLine || "").trim();
|
||||||
|
if (!rest && out.length < SIGNBOARD_LINES) { out.push(""); continue; }
|
||||||
|
while (rest && out.length < SIGNBOARD_LINES) {
|
||||||
|
out.push(sliceGraphemes(rest, SIGNBOARD_CHARS_PER_LINE));
|
||||||
|
rest = Array.from(rest).slice(SIGNBOARD_CHARS_PER_LINE).join("");
|
||||||
|
}
|
||||||
|
if (out.length >= SIGNBOARD_LINES) break;
|
||||||
|
}
|
||||||
|
return out.slice(0, SIGNBOARD_LINES).join("\n").replace(/\n+$/g, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function signboardVisibleLength(text = "") {
|
||||||
|
return Array.from(String(text || "").replace(/\n/g, "")).length;
|
||||||
|
}
|
||||||
|
function openSignboardEditor(sign, worldRef) {
|
||||||
|
if (!sign) return false;
|
||||||
|
const dialog = ensureSignboardEditor();
|
||||||
|
const area = dialog.querySelector("#signboardEditorText");
|
||||||
|
const count = dialog.querySelector("#signboardEditorCount");
|
||||||
|
const apply = dialog.querySelector("#signboardEditorApply");
|
||||||
|
const cancel = dialog.querySelector("#signboardEditorCancel");
|
||||||
|
const clear = dialog.querySelector("#signboardEditorClear");
|
||||||
|
const closeBtn = dialog.querySelector("#signboardEditorClose");
|
||||||
|
const close = () => {
|
||||||
|
dialog.classList.add("hidden");
|
||||||
|
dialog.__sign = null;
|
||||||
|
dialog.__worldRef = null;
|
||||||
|
};
|
||||||
|
const syncCount = () => {
|
||||||
|
if (!area || !count) return;
|
||||||
|
count.textContent = `${signboardVisibleLength(area.value || "")}/${SIGNBOARD_TEXT_LIMIT}`;
|
||||||
|
};
|
||||||
|
const commit = (value) => {
|
||||||
|
const next = sanitizeSignboardText(value);
|
||||||
|
sign.text = next;
|
||||||
|
sign.textEditedAt = worldRef?.time || 0;
|
||||||
|
worldRef?.log?.(sign.text ? `\u770b\u677f\u3092\u66f8\u304d\u63db\u3048\u305f\u3002` : `\u770b\u677f\u306e\u6587\u5b57\u3092\u6d88\u3057\u305f\u3002`, "observe");
|
||||||
|
worldRef.drawListDirty = true;
|
||||||
|
global.render?.();
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
dialog.__sign = sign;
|
||||||
|
dialog.__worldRef = worldRef;
|
||||||
|
if (area) {
|
||||||
|
area.value = sanitizeSignboardText(sign.text || "");
|
||||||
|
area.oninput = () => {
|
||||||
|
const clean = sanitizeSignboardText(area.value || "");
|
||||||
|
if (area.value !== clean && signboardVisibleLength(area.value || "") > SIGNBOARD_TEXT_LIMIT) area.value = clean;
|
||||||
|
syncCount();
|
||||||
|
};
|
||||||
|
setTimeout(() => { area.focus(); area.select(); syncCount(); }, 0);
|
||||||
|
}
|
||||||
|
if (apply) apply.onclick = () => commit(area?.value || "");
|
||||||
|
if (clear) clear.onclick = () => commit("");
|
||||||
|
if (cancel) cancel.onclick = close;
|
||||||
|
if (closeBtn) closeBtn.onclick = close;
|
||||||
|
dialog.onclick = (e) => { if (e.target === dialog) close(); };
|
||||||
|
dialog.classList.remove("hidden");
|
||||||
|
syncCount();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isEditableItem(item) {
|
||||||
|
return item && !item.dead && item.type === "signboard";
|
||||||
|
}
|
||||||
|
|
||||||
|
function findEditableItemAt(worldRef, x, y) {
|
||||||
|
let best = null;
|
||||||
|
let bestD = Infinity;
|
||||||
|
for (let i = (worldRef?.items || []).length - 1; i >= 0; i--) {
|
||||||
|
const it = worldRef.items[i];
|
||||||
|
if (!isEditableItem(it)) continue;
|
||||||
|
const d = distXY(x, y, it.x, it.y);
|
||||||
|
if (d <= Math.max(54, (it.r || 24) * 2.4) && d < bestD) { best = it; bestD = d; }
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditableItemEditor(item, worldRef) {
|
||||||
|
if (!isEditableItem(item)) return false;
|
||||||
|
if (item.type === "signboard") return openSignboardEditor(item, worldRef);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlacementTool(tool) {
|
||||||
|
const def = typeof toolDefinition === "function" ? toolDefinition(tool) : null;
|
||||||
|
return Boolean(def?.placeable && typeof toolItemType === "function" && toolItemType(tool));
|
||||||
|
}
|
||||||
|
|
||||||
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
||||||
fencePlacementBlocked(item) {
|
fencePlacementBlocked(item) {
|
||||||
if (!item || !this.isFenceType(item.type)) return false;
|
if (!item || !this.isFenceType(item.type)) return false;
|
||||||
|
|
@ -171,6 +301,7 @@
|
||||||
const d = distXY(x, y, it.x, it.y);
|
const d = distXY(x, y, it.x, it.y);
|
||||||
if (d <= hit && d < foundItemD) { foundItem = it; foundItemD = d; }
|
if (d <= hit && d < foundItemD) { foundItem = it; foundItemD = d; }
|
||||||
}
|
}
|
||||||
|
if (foundItem && isPinType(foundItem.type) && foundItem.pinState === "lodged") return foundItem;
|
||||||
if (foundT && foundItem) {
|
if (foundT && foundItem) {
|
||||||
const td = distXY(x, y, foundT.x, foundT.y);
|
const td = distXY(x, y, foundT.x, foundT.y);
|
||||||
return td <= foundItemD + 8 ? foundT : foundItem;
|
return td <= foundItemD + 8 ? foundT : foundItem;
|
||||||
|
|
@ -213,28 +344,21 @@
|
||||||
|
|
||||||
handleClick(x, y) {
|
handleClick(x, y) {
|
||||||
const tool = this.tool;
|
const tool = this.tool;
|
||||||
|
if (!isPlacementTool(tool) && !["delete", "poke", "pinch", "water_hose", "new"].includes(tool)) {
|
||||||
|
const editable = findEditableItemAt(this, x, y);
|
||||||
|
if (editable && openEditableItemEditor(editable, this)) {
|
||||||
|
this.selected = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (tool === "observe") {
|
if (tool === "observe") {
|
||||||
let found = null;
|
let found = null;
|
||||||
for (let i = this.tarinai.length - 1; i >= 0; i--) {
|
for (let i = this.tarinai.length - 1; i >= 0; i--) {
|
||||||
if (!this.isTarinaiHiddenInNestBox(this.tarinai[i]) && this.tarinai[i].contains(x, y)) { found = this.tarinai[i]; break; }
|
if (!this.isTarinaiHiddenInNestBox(this.tarinai[i]) && this.tarinai[i].contains(x, y)) { found = this.tarinai[i]; break; }
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
let sign = null;
|
const editable = findEditableItemAt(this, x, y);
|
||||||
let bestD = Infinity;
|
if (editable && openEditableItemEditor(editable, this)) {
|
||||||
for (let i = this.items.length - 1; i >= 0; i--) {
|
|
||||||
const it = this.items[i];
|
|
||||||
if (!it || it.dead || it.type !== "signboard") continue;
|
|
||||||
const d = distXY(x, y, it.x, it.y);
|
|
||||||
if (d <= Math.max(54, (it.r || 24) * 2.4) && d < bestD) { sign = it; bestD = d; }
|
|
||||||
}
|
|
||||||
if (sign) {
|
|
||||||
const current = String(sign.text || "");
|
|
||||||
const next = typeof prompt === "function" ? prompt("看板のテキスト", current) : current;
|
|
||||||
if (next !== null) {
|
|
||||||
sign.text = String(next || "").replace(/[\r\n]+/g, " ").slice(0, 24).trim();
|
|
||||||
sign.textEditedAt = this.time || 0;
|
|
||||||
this.log(sign.text ? `看板を書き換えた。` : `看板の文字を消した。`, "observe");
|
|
||||||
}
|
|
||||||
this.selected = null;
|
this.selected = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,22 @@
|
||||||
return { box: best, occupants, capacity };
|
return { box: best, occupants, capacity };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pointerOwnedBedInfo() {
|
||||||
|
const p = this.pointer;
|
||||||
|
if (!p?.inside) return null;
|
||||||
|
let best = null, bestD = Infinity;
|
||||||
|
for (const it of this.nearbyItems(p.x, p.y, 140)) {
|
||||||
|
if (!it || it.dead || it.type !== "grass_bed") continue;
|
||||||
|
const d = distXY(p.x, p.y, it.x, it.y);
|
||||||
|
if (d < bestD && d <= Math.max(60, (it.r || 24) * 2.0)) { best = it; bestD = d; }
|
||||||
|
}
|
||||||
|
if (!best) return null;
|
||||||
|
const owner = (this.tarinai || []).find(t => t && !t.dead && t.id === best.ownerId) || null;
|
||||||
|
return { bed: best, owner };
|
||||||
|
},
|
||||||
|
|
||||||
nestBoxToolTipCount() {
|
nestBoxToolTipCount() {
|
||||||
const pointed = this.pointerNestBoxInfo?.();
|
const pointed = this.pointerNestBoxInfo?.();
|
||||||
if (pointed?.box) return { current: pointed.occupants.length, max: pointed.capacity || this.nestBoxCapacity(pointed.box) };
|
if (pointed?.box) return { current: pointed.occupants.length, max: pointed.capacity || this.nestBoxCapacity(pointed.box) };
|
||||||
|
|
@ -243,17 +259,17 @@
|
||||||
|
|
||||||
|
|
||||||
const grassCount = this.itemCounts.grass || 0;
|
const grassCount = this.itemCounts.grass || 0;
|
||||||
if (grassCount < Math.min(24, this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 96)) && Math.random() < dt * 0.036) {
|
if (grassCount < Math.min(54, this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 180)) && Math.random() < dt * 0.082) {
|
||||||
const spot = this.findGrassPlantingSpot(rand(60, this.w - 60), rand(60, this.h - 60), {
|
const spot = this.findGrassPlantingSpot(rand(60, this.w - 60), rand(60, this.h - 60), {
|
||||||
allowOriginal: true,
|
allowOriginal: true,
|
||||||
minRadius: 28,
|
minRadius: 28,
|
||||||
maxRadius: 140,
|
maxRadius: 140,
|
||||||
attempts: 24,
|
attempts: 36,
|
||||||
});
|
});
|
||||||
if (spot) {
|
if (spot) {
|
||||||
const sprout = new Item("grass", spot.x, spot.y);
|
const sprout = new Item("grass", spot.x, spot.y);
|
||||||
sprout.amount = rand(22, 48);
|
sprout.amount = rand(30, 62);
|
||||||
sprout.growth = rand(0.12, 0.30);
|
sprout.growth = rand(0.18, 0.38);
|
||||||
this.items.push(sprout);
|
this.items.push(sprout);
|
||||||
this.itemCounts.grass = grassCount + 1;
|
this.itemCounts.grass = grassCount + 1;
|
||||||
this.markTerrainDirty?.("grass-spawn");
|
this.markTerrainDirty?.("grass-spawn");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const APP_VERSION = "15.24.2";
|
const APP_VERSION = "15.24.14";
|
||||||
const CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
|
const CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
|
||||||
const v = `v=${APP_VERSION}`;
|
const v = `v=${APP_VERSION}`;
|
||||||
// Generated from app_manifest.json. Run scripts/generate_app_files.py after changing static files.
|
// Generated from app_manifest.json. Run scripts/generate_app_files.py after changing static files.
|
||||||
|
|
@ -33,6 +33,15 @@ function isStaticAsset(request) {
|
||||||
return /\.(?:js|css|webp|png|jpg|jpeg|gif|svg|mp3|wav|woff2?)$/i.test(url.pathname);
|
return /\.(?:js|css|webp|png|jpg|jpeg|gif|svg|mp3|wav|woff2?)$/i.test(url.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cacheableResponse(response) {
|
||||||
|
return response && response.status === 200 && response.type !== "opaque";
|
||||||
|
}
|
||||||
|
|
||||||
|
function putCacheSafely(request, response) {
|
||||||
|
if (!cacheableResponse(response)) return Promise.resolve(false);
|
||||||
|
return caches.open(CACHE_NAME).then(cache => cache.put(request, response.clone())).then(() => true).catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
self.addEventListener("fetch", (event) => {
|
self.addEventListener("fetch", (event) => {
|
||||||
const request = event.request;
|
const request = event.request;
|
||||||
if (request.method !== "GET") return;
|
if (request.method !== "GET") return;
|
||||||
|
|
@ -41,16 +50,14 @@ self.addEventListener("fetch", (event) => {
|
||||||
if (/service-worker\.js$/i.test(url.pathname)) return;
|
if (/service-worker\.js$/i.test(url.pathname)) return;
|
||||||
if (request.mode === "navigate" || /index\.html$/i.test(url.pathname)) {
|
if (request.mode === "navigate" || /index\.html$/i.test(url.pathname)) {
|
||||||
event.respondWith(fetch(request).then(response => {
|
event.respondWith(fetch(request).then(response => {
|
||||||
const copy = response.clone();
|
putCacheSafely(request, response);
|
||||||
caches.open(CACHE_NAME).then(cache => cache.put(request, copy));
|
|
||||||
return response;
|
return response;
|
||||||
}).catch(() => caches.match(request).then(cached => cached || caches.match("./index.html"))));
|
}).catch(() => caches.match(request).then(cached => cached || caches.match("./index.html"))));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isStaticAsset(request)) {
|
if (isStaticAsset(request)) {
|
||||||
event.respondWith(caches.match(request).then(cached => cached || fetch(request).then(response => {
|
event.respondWith(caches.match(request).then(cached => cached || fetch(request).then(response => {
|
||||||
const copy = response.clone();
|
putCacheSafely(request, response);
|
||||||
caches.open(CACHE_NAME).then(cache => cache.put(request, copy));
|
|
||||||
return response;
|
return response;
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue