published

This commit is contained in:
33333-33333 2026-06-26 22:35:26 +09:00
commit 0f1cb2fe6c
10 changed files with 497 additions and 198 deletions

View file

@ -1,5 +1,5 @@
{
"version": "38.00.00",
"version": "39.00.00",
"css": [
"css/base.css",
"css/layout.css",

View file

@ -7,11 +7,11 @@
<link rel="icon" type="image/png" href="assets/ui/favicon.png?v=16.07.17" />
<link rel="apple-touch-icon" href="assets/ui/apple-touch-icon.png?v=16.07.17" />
<link rel="shortcut icon" href="favicon.ico?v=16.07.17" />
<link rel="stylesheet" href="css/base.css?v=38.00.00" />
<link rel="stylesheet" href="css/layout.css?v=38.00.00" />
<link rel="stylesheet" href="css/panel.css?v=38.00.00" />
<link rel="stylesheet" href="css/components.css?v=38.00.00" />
<link rel="stylesheet" href="css/mobile.css?v=38.00.00" />
<link rel="stylesheet" href="css/base.css?v=39.00.00" />
<link rel="stylesheet" href="css/layout.css?v=39.00.00" />
<link rel="stylesheet" href="css/panel.css?v=39.00.00" />
<link rel="stylesheet" href="css/components.css?v=39.00.00" />
<link rel="stylesheet" href="css/mobile.css?v=39.00.00" />
</head>
<body>
<div id="loadingScreen" class="loading-screen" aria-live="polite">
@ -189,140 +189,140 @@
</div>
<div id="logPushOverlay" class="log-push-overlay" aria-live="polite" aria-atomic="false"></div>
<script src="js/version.js?v=38.00.00" defer></script>
<script src="js/event_bus.js?v=38.00.00" defer></script>
<script src="js/registry_base.js?v=38.00.00" defer></script>
<script src="js/disease_registry.js?v=38.00.00" defer></script>
<script src="js/sound_pack.js?v=38.00.00" defer></script>
<script src="js/item_registry.js?v=38.00.00" defer></script>
<script src="js/data.js?v=38.00.00" defer></script>
<script src="js/ground_types.js?v=38.00.00" defer></script>
<script src="js/input_mode_manager.js?v=38.00.00" defer></script>
<script src="js/math.js?v=38.00.00" defer></script>
<script src="js/physics_helpers.js?v=38.00.00" defer></script>
<script src="js/collision_footprint_system.js?v=38.00.00" defer></script>
<script src="js/mechanical_system.js?v=38.00.00" defer></script>
<script src="js/constraint_system.js?v=38.00.00" defer></script>
<script src="js/assets.js?v=38.00.00" defer></script>
<script src="js/audio.js?v=38.00.00" defer></script>
<script src="js/perf_profiler.js?v=38.00.00" defer></script>
<script src="js/render.js?v=38.00.00" defer></script>
<script src="js/sim_core.js?v=38.00.00" defer></script>
<script src="js/tarinai_seed_factory.js?v=38.00.00" defer></script>
<script src="js/structures.js?v=38.00.00" defer></script>
<script src="js/items.js?v=38.00.00" defer></script>
<script src="js/item_type_initializers.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_support.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_runtime.js?v=38.00.00" defer></script>
<script src="js/item_update_policy.js?v=38.00.00" defer></script>
<script src="js/item_dynamic_tool_system.js?v=38.00.00" defer></script>
<script src="js/item_dynamic_ball_system.js?v=38.00.00" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=38.00.00" defer></script>
<script src="js/item_dynamic_pin_system.js?v=38.00.00" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=38.00.00" defer></script>
<script src="js/item_dynamic_system.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=38.00.00" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=38.00.00" defer></script>
<script src="js/item_runtime.js?v=38.00.00" defer></script>
<script src="js/structure_lifecycle.js?v=38.00.00" defer></script>
<script src="js/item_render_runtime.js?v=38.00.00" defer></script>
<script src="js/ants.js?v=38.00.00" defer></script>
<script src="js/health.js?v=38.00.00" defer></script>
<script src="js/tarinai.js?v=38.00.00" defer></script>
<script src="js/tarinai_action_spec.js?v=38.00.00" defer></script>
<script src="js/tarinai_behavior_state.js?v=38.00.00" defer></script>
<script src="js/tarinai_identity_social.js?v=38.00.00" defer></script>
<script src="js/tarinai_action_state.js?v=38.00.00" defer></script>
<script src="js/tarinai_disease_nest.js?v=38.00.00" defer></script>
<script src="js/tarinai_item_effects.js?v=38.00.00" defer></script>
<script src="js/tarinai_needs_core.js?v=38.00.00" defer></script>
<script src="js/tarinai_behavior_text.js?v=38.00.00" defer></script>
<script src="js/tarinai_forced_behavior.js?v=38.00.00" defer></script>
<script src="js/tarinai_item_targeting.js?v=38.00.00" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=38.00.00" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=38.00.00" defer></script>
<script src="js/tarinai_building_behavior.js?v=38.00.00" defer></script>
<script src="js/tarinai_action_definitions.js?v=38.00.00" defer></script>
<script src="js/tarinai_needs_items.js?v=38.00.00" defer></script>
<script src="js/tarinai_forced_action_planner_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_action_planner_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_need_planner_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=38.00.00" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_contact_item_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_sunbath_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_local_environment_system.js?v=38.00.00" defer></script>
<script src="js/tarinai_social_move_life.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_step_frame.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_step_ai.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_step_environment.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_step_movement.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_step_health.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_pipeline.js?v=38.00.00" defer></script>
<script src="js/tarinai_runtime.js?v=38.00.00" defer></script>
<script src="js/tarinai_render.js?v=38.00.00" defer></script>
<script src="js/world.js?v=38.00.00" defer></script>
<script src="js/world_view.js?v=38.00.00" defer></script>
<script src="js/family_graph.js?v=38.00.00" defer></script>
<script src="js/world_family_social.js?v=38.00.00" defer></script>
<script src="js/world_combat_effects.js?v=38.00.00" defer></script>
<script src="js/world_environment.js?v=38.00.00" defer></script>
<script src="js/world_spatial_budget.js?v=38.00.00" defer></script>
<script src="js/world_ants_system.js?v=38.00.00" defer></script>
<script src="js/weather_system.js?v=38.00.00" defer></script>
<script src="js/item_update_scheduler.js?v=38.00.00" defer></script>
<script src="js/simulation_runtime_helpers.js?v=38.00.00" defer></script>
<script src="js/tarinai_update_policy.js?v=38.00.00" defer></script>
<script src="js/simulation_environment_system.js?v=38.00.00" defer></script>
<script src="js/simulation_item_ant_system.js?v=38.00.00" defer></script>
<script src="js/simulation_effects_system.js?v=38.00.00" defer></script>
<script src="js/simulation_creature_system.js?v=38.00.00" defer></script>
<script src="js/simulation_maintenance_system.js?v=38.00.00" defer></script>
<script src="js/simulation_ambient_system.js?v=38.00.00" defer></script>
<script src="js/simulation_systems.js?v=38.00.00" defer></script>
<script src="js/world_update_phases.js?v=38.00.00" defer></script>
<script src="js/system_order.js?v=38.00.00" defer></script>
<script src="js/simulation.js?v=38.00.00" defer></script>
<script src="js/world_update.js?v=38.00.00" defer></script>
<script src="js/world_tool_actions.js?v=38.00.00" defer></script>
<script src="js/world_placement_log.js?v=38.00.00" defer></script>
<script src="js/world_event_effects.js?v=38.00.00" defer></script>
<script src="js/command_dispatcher.js?v=38.00.00" defer></script>
<script src="js/text_catalog.js?v=38.00.00" defer></script>
<script src="js/ui.js?v=38.00.00" defer></script>
<script src="js/ui_log.js?v=38.00.00" defer></script>
<script src="js/ui_helpers.js?v=38.00.00" defer></script>
<script src="js/ui_selected.js?v=38.00.00" defer></script>
<script src="js/save_schema.js?v=38.00.00" defer></script>
<script src="js/snapshot_system.js?v=38.00.00" defer></script>
<script src="js/restore_coordinator.js?v=38.00.00" defer></script>
<script src="js/history_system.js?v=38.00.00" defer></script>
<script src="js/save_codec.js?v=38.00.00" defer></script>
<script src="js/save_storage.js?v=38.00.00" defer></script>
<script src="js/save_system.js?v=38.00.00" defer></script>
<script src="js/patch_helpers.js?v=38.00.00" defer></script>
<script src="js/ui_tooltips.js?v=38.00.00" defer></script>
<script src="js/ui_layout_dialogs.js?v=38.00.00" defer></script>
<script src="js/ui_ground.js?v=38.00.00" defer></script>
<script src="js/ui_tools.js?v=38.00.00" defer></script>
<script src="js/ui_input_shared.js?v=38.00.00" defer></script>
<script src="js/ui_input_touch.js?v=38.00.00" defer></script>
<script src="js/ui_input_mouse.js?v=38.00.00" defer></script>
<script src="js/ui_bind.js?v=38.00.00" defer></script>
<script src="js/ui_charts.js?v=38.00.00" defer></script>
<script src="js/ui_family_data.js?v=38.00.00" defer></script>
<script src="js/ui_family_async.js?v=38.00.00" defer></script>
<script src="js/ui_family_layout.js?v=38.00.00" defer></script>
<script src="js/ui_family_paths.js?v=38.00.00" defer></script>
<script src="js/ui_family_render.js?v=38.00.00" defer></script>
<script src="js/main.js?v=38.00.00" defer></script>
<script src="js/debug_tools.js?v=38.00.00" defer></script>
<script src="js/version.js?v=39.00.00" defer></script>
<script src="js/event_bus.js?v=39.00.00" defer></script>
<script src="js/registry_base.js?v=39.00.00" defer></script>
<script src="js/disease_registry.js?v=39.00.00" defer></script>
<script src="js/sound_pack.js?v=39.00.00" defer></script>
<script src="js/item_registry.js?v=39.00.00" defer></script>
<script src="js/data.js?v=39.00.00" defer></script>
<script src="js/ground_types.js?v=39.00.00" defer></script>
<script src="js/input_mode_manager.js?v=39.00.00" defer></script>
<script src="js/math.js?v=39.00.00" defer></script>
<script src="js/physics_helpers.js?v=39.00.00" defer></script>
<script src="js/collision_footprint_system.js?v=39.00.00" defer></script>
<script src="js/mechanical_system.js?v=39.00.00" defer></script>
<script src="js/constraint_system.js?v=39.00.00" defer></script>
<script src="js/assets.js?v=39.00.00" defer></script>
<script src="js/audio.js?v=39.00.00" defer></script>
<script src="js/perf_profiler.js?v=39.00.00" defer></script>
<script src="js/render.js?v=39.00.00" defer></script>
<script src="js/sim_core.js?v=39.00.00" defer></script>
<script src="js/tarinai_seed_factory.js?v=39.00.00" defer></script>
<script src="js/structures.js?v=39.00.00" defer></script>
<script src="js/items.js?v=39.00.00" defer></script>
<script src="js/item_type_initializers.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_support.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_runtime.js?v=39.00.00" defer></script>
<script src="js/item_update_policy.js?v=39.00.00" defer></script>
<script src="js/item_dynamic_tool_system.js?v=39.00.00" defer></script>
<script src="js/item_dynamic_ball_system.js?v=39.00.00" defer></script>
<script src="js/item_dynamic_duplicator_system.js?v=39.00.00" defer></script>
<script src="js/item_dynamic_pin_system.js?v=39.00.00" defer></script>
<script src="js/item_dynamic_zunchi_system.js?v=39.00.00" defer></script>
<script src="js/item_dynamic_system.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_decay_system.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_growth_system.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_step_frame.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_step_decay.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_step_dynamic.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_step_growth.js?v=39.00.00" defer></script>
<script src="js/item_lifecycle_pipeline.js?v=39.00.00" defer></script>
<script src="js/item_runtime.js?v=39.00.00" defer></script>
<script src="js/structure_lifecycle.js?v=39.00.00" defer></script>
<script src="js/item_render_runtime.js?v=39.00.00" defer></script>
<script src="js/ants.js?v=39.00.00" defer></script>
<script src="js/health.js?v=39.00.00" defer></script>
<script src="js/tarinai.js?v=39.00.00" defer></script>
<script src="js/tarinai_action_spec.js?v=39.00.00" defer></script>
<script src="js/tarinai_behavior_state.js?v=39.00.00" defer></script>
<script src="js/tarinai_identity_social.js?v=39.00.00" defer></script>
<script src="js/tarinai_action_state.js?v=39.00.00" defer></script>
<script src="js/tarinai_disease_nest.js?v=39.00.00" defer></script>
<script src="js/tarinai_item_effects.js?v=39.00.00" defer></script>
<script src="js/tarinai_needs_core.js?v=39.00.00" defer></script>
<script src="js/tarinai_behavior_text.js?v=39.00.00" defer></script>
<script src="js/tarinai_forced_behavior.js?v=39.00.00" defer></script>
<script src="js/tarinai_item_targeting.js?v=39.00.00" defer></script>
<script src="js/tarinai_consumable_behavior.js?v=39.00.00" defer></script>
<script src="js/tarinai_social_action_runtime.js?v=39.00.00" defer></script>
<script src="js/tarinai_building_behavior.js?v=39.00.00" defer></script>
<script src="js/tarinai_action_definitions.js?v=39.00.00" defer></script>
<script src="js/tarinai_needs_items.js?v=39.00.00" defer></script>
<script src="js/tarinai_forced_action_planner_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_action_planner_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_need_planner_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_item_interaction_context.js?v=39.00.00" defer></script>
<script src="js/tarinai_food_interaction_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_contact_item_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_item_interaction_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_sunbath_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_cursor_care_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_local_environment_system.js?v=39.00.00" defer></script>
<script src="js/tarinai_social_move_life.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_step_frame.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_step_ai.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_step_environment.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_step_movement.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_step_health.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_pipeline.js?v=39.00.00" defer></script>
<script src="js/tarinai_runtime.js?v=39.00.00" defer></script>
<script src="js/tarinai_render.js?v=39.00.00" defer></script>
<script src="js/world.js?v=39.00.00" defer></script>
<script src="js/world_view.js?v=39.00.00" defer></script>
<script src="js/family_graph.js?v=39.00.00" defer></script>
<script src="js/world_family_social.js?v=39.00.00" defer></script>
<script src="js/world_combat_effects.js?v=39.00.00" defer></script>
<script src="js/world_environment.js?v=39.00.00" defer></script>
<script src="js/world_spatial_budget.js?v=39.00.00" defer></script>
<script src="js/world_ants_system.js?v=39.00.00" defer></script>
<script src="js/weather_system.js?v=39.00.00" defer></script>
<script src="js/item_update_scheduler.js?v=39.00.00" defer></script>
<script src="js/simulation_runtime_helpers.js?v=39.00.00" defer></script>
<script src="js/tarinai_update_policy.js?v=39.00.00" defer></script>
<script src="js/simulation_environment_system.js?v=39.00.00" defer></script>
<script src="js/simulation_item_ant_system.js?v=39.00.00" defer></script>
<script src="js/simulation_effects_system.js?v=39.00.00" defer></script>
<script src="js/simulation_creature_system.js?v=39.00.00" defer></script>
<script src="js/simulation_maintenance_system.js?v=39.00.00" defer></script>
<script src="js/simulation_ambient_system.js?v=39.00.00" defer></script>
<script src="js/simulation_systems.js?v=39.00.00" defer></script>
<script src="js/world_update_phases.js?v=39.00.00" defer></script>
<script src="js/system_order.js?v=39.00.00" defer></script>
<script src="js/simulation.js?v=39.00.00" defer></script>
<script src="js/world_update.js?v=39.00.00" defer></script>
<script src="js/world_tool_actions.js?v=39.00.00" defer></script>
<script src="js/world_placement_log.js?v=39.00.00" defer></script>
<script src="js/world_event_effects.js?v=39.00.00" defer></script>
<script src="js/command_dispatcher.js?v=39.00.00" defer></script>
<script src="js/text_catalog.js?v=39.00.00" defer></script>
<script src="js/ui.js?v=39.00.00" defer></script>
<script src="js/ui_log.js?v=39.00.00" defer></script>
<script src="js/ui_helpers.js?v=39.00.00" defer></script>
<script src="js/ui_selected.js?v=39.00.00" defer></script>
<script src="js/save_schema.js?v=39.00.00" defer></script>
<script src="js/snapshot_system.js?v=39.00.00" defer></script>
<script src="js/restore_coordinator.js?v=39.00.00" defer></script>
<script src="js/history_system.js?v=39.00.00" defer></script>
<script src="js/save_codec.js?v=39.00.00" defer></script>
<script src="js/save_storage.js?v=39.00.00" defer></script>
<script src="js/save_system.js?v=39.00.00" defer></script>
<script src="js/patch_helpers.js?v=39.00.00" defer></script>
<script src="js/ui_tooltips.js?v=39.00.00" defer></script>
<script src="js/ui_layout_dialogs.js?v=39.00.00" defer></script>
<script src="js/ui_ground.js?v=39.00.00" defer></script>
<script src="js/ui_tools.js?v=39.00.00" defer></script>
<script src="js/ui_input_shared.js?v=39.00.00" defer></script>
<script src="js/ui_input_touch.js?v=39.00.00" defer></script>
<script src="js/ui_input_mouse.js?v=39.00.00" defer></script>
<script src="js/ui_bind.js?v=39.00.00" defer></script>
<script src="js/ui_charts.js?v=39.00.00" defer></script>
<script src="js/ui_family_data.js?v=39.00.00" defer></script>
<script src="js/ui_family_async.js?v=39.00.00" defer></script>
<script src="js/ui_family_layout.js?v=39.00.00" defer></script>
<script src="js/ui_family_paths.js?v=39.00.00" defer></script>
<script src="js/ui_family_render.js?v=39.00.00" defer></script>
<script src="js/main.js?v=39.00.00" defer></script>
<script src="js/debug_tools.js?v=39.00.00" defer></script>
</body>
</html>

View file

@ -30,22 +30,129 @@
function resolveEndpointObject(endpoint, worldRef) {
if (!endpoint || !worldRef) return null;
const allowFuzzy = endpoint.fuzzyResolve === true || endpoint.legacyFuzzy === true;
if (endpoint.kind === "tarinai") {
let found = (worldRef.tarinai || []).find(t => t && !t.dead && t.id === endpoint.id);
if (!found && Number.isFinite(endpoint.x) && Number.isFinite(endpoint.y)) {
if (!found && allowFuzzy && Number.isFinite(endpoint.x) && Number.isFinite(endpoint.y)) {
found = (worldRef.tarinai || []).find(t => t && !t.dead && distXY(t.x, t.y, endpoint.x, endpoint.y) <= Math.max(40, (t.radius || 20) * 1.6));
if (found) endpoint.id = found.id;
if (found) {
endpoint.id = found.id;
endpoint.fuzzyResolve = false;
endpoint.legacyFuzzy = false;
}
}
return found || null;
}
let found = (worldRef.items || []).find(it => it && !it.dead && it.id === endpoint.id);
if (!found && Number.isFinite(endpoint.x) && Number.isFinite(endpoint.y)) {
if (!found && allowFuzzy && Number.isFinite(endpoint.x) && Number.isFinite(endpoint.y)) {
found = (worldRef.items || []).find(it => it && !it.dead && it.type === endpoint.type && distXY(it.x, it.y, endpoint.x, endpoint.y) <= Math.max(44, (it.r || 20) * 1.8));
if (found) endpoint.id = found.id;
if (found) {
endpoint.id = found.id;
endpoint.fuzzyResolve = false;
endpoint.legacyFuzzy = false;
}
}
return found || null;
}
function itemAngle(item) {
return typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item?.angle) || 0);
}
function isMechanicalLinkTarget(item) {
return Boolean(item && !item.dead && global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type));
}
function localToWorld(item, lx, ly) {
const a = itemAngle(item);
const c = Math.cos(a), s = Math.sin(a);
return {
x: (Number(item?.x || 0) || 0) + lx * c - ly * s,
y: (Number(item?.y || 0) || 0) + lx * s + ly * c,
};
}
function worldToLocal(item, x, y) {
const a = itemAngle(item);
const dx = (Number(x || 0) || 0) - (Number(item?.x || 0) || 0);
const dy = (Number(y || 0) || 0) - (Number(item?.y || 0) || 0);
const c = Math.cos(-a), s = Math.sin(-a);
return { x: dx * c - dy * s, y: dx * s + dy * c };
}
function nearestMechanicalLocalPoint(item, lx, ly) {
if (!isMechanicalLinkTarget(item)) return null;
const segments = global.TarinaiMechanicalSystem?.sanitizeSegments?.(item) || [];
let best = null;
for (const seg of segments) {
if (!Array.isArray(seg) || seg.length < 4) continue;
const x1 = Number(seg[0]) || 0, y1 = Number(seg[1]) || 0;
const x2 = Number(seg[2]) || 0, y2 = Number(seg[3]) || 0;
const vx = x2 - x1, vy = y2 - y1;
const len2 = vx * vx + vy * vy;
if (len2 < 16) continue;
const t = clamp(((lx - x1) * vx + (ly - y1) * vy) / len2, 0, 1);
const x = x1 + vx * t;
const y = y1 + vy * t;
const d = Math.hypot(lx - x, ly - y);
if (!best || d < best.d) best = { x, y, d };
}
return best;
}
function snapEndpointToTarget(endpoint, worldRef) {
const obj = resolveEndpointObject(endpoint, worldRef);
if (!endpoint || !isMechanicalLinkTarget(obj)) return endpoint;
let local;
if (!endpoint.center && (Number.isFinite(Number(endpoint.localX)) || Number.isFinite(Number(endpoint.localY)))) {
local = { x: Number(endpoint.localX || 0) || 0, y: Number(endpoint.localY || 0) || 0 };
} else if (Number.isFinite(Number(endpoint.x)) && Number.isFinite(Number(endpoint.y))) {
local = worldToLocal(obj, endpoint.x, endpoint.y);
} else {
local = { x: 0, y: 0 };
}
const snapped = nearestMechanicalLocalPoint(obj, local.x, local.y);
if (!snapped) return endpoint;
const world = localToWorld(obj, snapped.x, snapped.y);
endpoint.localX = snapped.x;
endpoint.localY = snapped.y;
endpoint.x = world.x;
endpoint.y = world.y;
endpoint.center = false;
return endpoint;
}
function remapLinksForEditedMechanicalItem(worldRef, target, reason = "mechanical-shape-edited") {
if (!worldRef || !isMechanicalLinkTarget(target)) return 0;
let changed = 0;
const remap = (endpoint) => {
if (!endpoint || endpoint.kind !== "item" || endpoint.id !== target.id) return false;
const beforeX = Number(endpoint.localX || 0) || 0;
const beforeY = Number(endpoint.localY || 0) || 0;
const beforeWorldX = Number(endpoint.x || 0) || 0;
const beforeWorldY = Number(endpoint.y || 0) || 0;
snapEndpointToTarget(endpoint, worldRef);
const localDelta = Math.hypot((Number(endpoint.localX || 0) || 0) - beforeX, (Number(endpoint.localY || 0) || 0) - beforeY);
const worldDelta = Math.hypot((Number(endpoint.x || 0) || 0) - beforeWorldX, (Number(endpoint.y || 0) || 0) - beforeWorldY);
return localDelta > 0.001 || worldDelta > 0.001;
};
for (const item of worldRef.items || []) {
if (!item || item.dead || (item.type !== "rope" && item.type !== "rod")) continue;
const a = remap(item.linkA);
const b = remap(item.linkB);
if (a || b) {
item.world = worldRef;
changed += 1;
}
}
if (remap(worldRef.pendingLinkEndpoint)) changed += 1;
if (changed) {
worldRef.drawListDirty = true;
worldRef.markSpatialDirty?.(reason);
}
return changed;
}
function endpointWorld(endpoint, worldRef, depth = 0, seen = null) {
const obj = resolveEndpointObject(endpoint, worldRef);
if (obj && (obj.type === "rope" || obj.type === "rod") && worldRef) obj.world = worldRef;
@ -66,7 +173,7 @@
const lx = Number(endpoint.localX || 0) || 0;
const ly = Number(endpoint.localY || 0) || 0;
if (endpoint.center || (!lx && !ly)) return { x: obj.x || 0, y: obj.y || 0, obj };
const a = typeof itemAngleFor === "function" ? itemAngleFor(obj) : (Number(obj.angle) || 0);
const a = itemAngle(obj);
const c = Math.cos(a), s = Math.sin(a);
return { x: (obj.x || 0) + lx * c - ly * s, y: (obj.y || 0) + lx * s + ly * c, obj };
}
@ -283,6 +390,8 @@
resolveEndpointObject,
endpointMass,
moveEndpointObject,
snapEndpointToTarget,
remapLinksForEditedMechanicalItem,
updateFlexibleLink,
updateRigidLink,
updateLink,
@ -290,5 +399,5 @@
});
global.TarinaiConstraintSystem = api;
global.TarinaiLinkRuntime = Object.freeze({ endpointWorld, pointSegmentDistance });
global.TarinaiLinkRuntime = Object.freeze({ endpointWorld, pointSegmentDistance, snapEndpointToTarget, remapLinksForEditedMechanicalItem });
})(typeof window !== "undefined" ? window : globalThis);

View file

@ -258,10 +258,23 @@ function drawOperationToolPreview(ctx, world) {
const type = world.copyBuffer.type || "";
const item = world.copyPreviewItemAt?.(p.x, p.y);
if (item) {
const pad = typeof CONFIG !== "undefined" ? (CONFIG.worldPadding ?? 0) : 0;
const rectOutside = (rect) => Boolean(rect && (rect.left < pad || rect.top < pad || rect.right > world.w - pad || rect.bottom > world.h - pad));
const circleOutside = (cx, cy, radius) => cx - radius < pad || cy - radius < pad || cx + radius > world.w - pad || cy + radius > world.h - pad;
const previewRects = world.solidObstacleRects?.(item) || [];
const boundsBlocked = previewRects.length
? previewRects.some(rr => rectOutside(rr.oriented ? {
left: (rr.cx || p.x) - (rr.halfW || 10) - (rr.halfH || 5),
right: (rr.cx || p.x) + (rr.halfW || 10) + (rr.halfH || 5),
top: (rr.cy || p.y) - (rr.halfW || 10) - (rr.halfH || 5),
bottom: (rr.cy || p.y) + (rr.halfW || 10) + (rr.halfH || 5),
} : rr))
: circleOutside(p.x, p.y, Math.max(14, (item.r || itemRadiusFor?.(type, 16) || 16) * 1.25));
const blocked = boundsBlocked || Boolean(world.placementBlocked?.(item) || world.fencePlacementBlocked?.(item));
let copyPreviewDrew = false;
if (typeof item.draw === "function") {
ctx.save();
ctx.globalAlpha = 0.74;
ctx.globalAlpha = blocked ? 0.44 : 0.74;
try { item.draw(ctx, world.time || 0, typeof getLightingState === "function" ? getLightingState(world) : null); copyPreviewDrew = true; } catch (_) { copyPreviewDrew = false; }
ctx.restore();
}
@ -273,18 +286,18 @@ function drawOperationToolPreview(ctx, world) {
const bctx = buffer.getContext("2d");
if (bctx && globalThis.drawToolItemPreview(bctx, type, { width: buffer.width, height: buffer.height, compact: true }) !== false) {
ctx.save();
ctx.globalAlpha = 0.74;
ctx.globalAlpha = blocked ? 0.44 : 0.74;
ctx.drawImage(buffer, p.x - previewSize / 2, p.y - previewSize / 2, previewSize, previewSize);
ctx.restore();
}
}
ctx.save();
ctx.globalAlpha = 0.50;
ctx.setLineDash([7, 5]);
ctx.lineWidth = 2;
ctx.strokeStyle = "rgba(128,92,198,0.86)";
ctx.fillStyle = "rgba(150,110,220,0.12)";
const rects = world.solidObstacleRects?.(item) || [];
ctx.globalAlpha = blocked ? 0.58 : 0.50;
ctx.setLineDash(blocked ? [5, 4] : [7, 5]);
ctx.lineWidth = blocked ? 2.2 : 2;
ctx.strokeStyle = blocked ? "rgba(194,70,58,0.88)" : "rgba(110,178,55,0.88)";
ctx.fillStyle = blocked ? "rgba(232,84,72,0.14)" : "rgba(190,236,92,0.15)";
const rects = previewRects;
if (rects.length) {
for (const rr of rects) {
if (rr.oriented) {

View file

@ -311,7 +311,7 @@
if (typeof t.refreshEffectiveSize === "function") t.refreshEffectiveSize();
return t;
}
function itemExtra(item, tarinaiIndex = new Map()) {
function itemExtra(item, tarinaiIndex = new Map(), itemIndex = new Map()) {
const type = item?.type || "";
if (item?.isStructure) {
const owner = tarinaiIndex.get(item.ownerId) ?? -1;
@ -346,7 +346,24 @@
];
if (type === "rope" || type === "rod") {
const cloneEndpoint = ep => ep ? { kind: ep.kind || "item", id: ep.id || "", type: ep.type || "", label: ep.label || "", localX: q(ep.localX || 0, 1), localY: q(ep.localY || 0, 1), center: ep.center ? 1 : 0, x: q(ep.x || 0, 1), y: q(ep.y || 0, 1), attachT: Number.isFinite(Number(ep.attachT)) ? q(ep.attachT, 1000) : null } : null;
const cloneEndpoint = ep => {
if (!ep) return null;
const kind = ep.kind || "item";
const refIdx = kind === "tarinai" ? (tarinaiIndex.get(ep.id) ?? -1) : (itemIndex.get(ep.id) ?? -1);
return {
kind,
id: ep.id || "",
refIdx,
type: ep.type || "",
label: ep.label || "",
localX: q(ep.localX || 0, 1),
localY: q(ep.localY || 0, 1),
center: ep.center ? 1 : 0,
x: q(ep.x || 0, 1),
y: q(ep.y || 0, 1),
attachT: Number.isFinite(Number(ep.attachT)) ? q(ep.attachT, 1000) : null,
};
};
return [cloneEndpoint(item.linkA), cloneEndpoint(item.linkB), q(item.linkLength || item.r * 2 || 80, 1), q(item.linkMidX || item.x || 0, 1), q(item.linkMidY || item.y || 0, 1), q(item.linkMidVX || 0, 10), q(item.linkMidVY || 0, 10)];
}
if (type === "duplicator") return [enumIndex(ITEM_TYPE_IDS, item.storedFoodType || "", -1)];
@ -359,12 +376,12 @@
if (typeof global.isServingFoodType === "function" && global.isServingFoodType(type)) return [q(item.foodServingsRemaining ?? item.amount, 10), q(item.foodServingsMax, 10)];
return [];
}
function compactItem(item, index, tarinaiIndex = new Map()) {
function compactItem(item, index, tarinaiIndex = new Map(), itemIndex = new Map()) {
const typeId = enumIndex(ITEM_TYPE_IDS, item?.type || "", -1);
if (typeId < 0 || item?.type === "trace") return null;
return [typeId, q(item?.x, 1), q(item?.y, 1), q(item?.amount ?? item?.hp, 10), itemExtra(item, tarinaiIndex)];
return [typeId, q(item?.x, 1), q(item?.y, 1), q(item?.amount ?? item?.hp, 10), itemExtra(item, tarinaiIndex, itemIndex)];
}
function applyItemExtra(item, extra, tarinaiList = []) {
function applyItemExtra(item, extra, tarinaiList = [], itemList = []) {
if (!Array.isArray(extra)) return;
const type = item.type || "";
if (item.isStructure) {
@ -426,7 +443,27 @@
item.r = Math.max(item.r || 64, Math.min(460, extent));
} else if (type === "rope" || type === "rod") {
const e = Array.isArray(extra) ? extra : [];
const normalizeEndpoint = ep => ep ? { kind: ep.kind || "item", id: ep.id || "", type: ep.type || "", label: ep.label || "", localX: u(ep.localX, 1, 0), localY: u(ep.localY, 1, 0), center: !!ep.center, x: u(ep.x, 1, item.x || 0), y: u(ep.y, 1, item.y || 0), attachT: ep.attachT == null ? null : clamp(u(ep.attachT, 1000, 0.5), 0, 1) } : null;
const normalizeEndpoint = ep => {
if (!ep) return null;
const kind = ep.kind || "item";
const refIdx = Number(ep.refIdx);
const indexedTarget = Number.isInteger(refIdx) && refIdx >= 0
? (kind === "tarinai" ? tarinaiList[refIdx] : itemList[refIdx])
: null;
return {
kind,
id: indexedTarget?.id || ep.id || "",
type: ep.type || indexedTarget?.type || "",
label: ep.label || indexedTarget?.name || "",
localX: u(ep.localX, 1, 0),
localY: u(ep.localY, 1, 0),
center: !!ep.center,
x: u(ep.x, 1, item.x || 0),
y: u(ep.y, 1, item.y || 0),
attachT: ep.attachT == null ? null : clamp(u(ep.attachT, 1000, 0.5), 0, 1),
fuzzyResolve: !indexedTarget && !Number.isInteger(refIdx),
};
};
item.linkA = normalizeEndpoint(e[0]);
item.linkB = normalizeEndpoint(e[1]);
item.linkLength = Math.max(24, u(e[2], 1, item.linkLength || 80));
@ -467,13 +504,13 @@
const itemRecords = [];
for (const it of (worldRef.items || [])) {
if (!it || it.dead || it.type === "trace" || enumIndex(ITEM_TYPE_IDS, it.type || "", -1) < 0) continue;
const row = compactItem(it, itemRecords.length, tarinaiIndex);
if (row) itemRecords.push({ item: it, row });
const typeId = enumIndex(ITEM_TYPE_IDS, it.type || "", -1);
itemRecords.push({ item: it, sortTypeId: typeId, sortX: q(it.x, 1), sortY: q(it.y, 1) });
}
itemRecords.sort((a, b) => (a.row[0] || 0) - (b.row[0] || 0) || mortonKeyXY(a.row[1], a.row[2]) - mortonKeyXY(b.row[1], b.row[2]) || String(a.item?.id || "").localeCompare(String(b.item?.id || "")));
itemRecords.sort((a, b) => (a.sortTypeId || 0) - (b.sortTypeId || 0) || mortonKeyXY(a.sortX, a.sortY) - mortonKeyXY(b.sortX, b.sortY) || String(a.item?.id || "").localeCompare(String(b.item?.id || "")));
const itemIndex = new Map();
itemRecords.forEach((rec, i) => { if (rec.item?.id) itemIndex.set(rec.item.id, i); });
const itemRows = itemRecords.map(rec => rec.row);
const itemRows = itemRecords.map((rec, i) => compactItem(rec.item, i, tarinaiIndex, itemIndex)).filter(Boolean);
return {
v: SNAPSHOT_VERSION,
a: "tj1",
@ -535,9 +572,13 @@
item.id = `li${idx}`;
item.world = worldRef;
item.amount = u(row[3], 10, item.amount || 1);
applyItemExtra(item, extra, worldRef.tarinai);
item.__savedExtra = extra;
worldRef.items.push(item);
}
for (const item of worldRef.items) {
applyItemExtra(item, item.__savedExtra || [], worldRef.tarinai, worldRef.items);
delete item.__savedExtra;
}
for (const t of worldRef.tarinai) {
if (Number.isInteger(t.__savedPinIdx) && t.__savedPinIdx >= 0) t.stuckPushpinId = worldRef.items[t.__savedPinIdx]?.id || null;
if (Number.isInteger(t.__savedNestIdx) && t.__savedNestIdx >= 0) t.insideNestBoxId = worldRef.items[t.__savedNestIdx]?.id || null;

View file

@ -125,9 +125,15 @@ function resizeFieldFromWheel(e) {
}
function selectTool(tool) {
const wasCopyArmed = world.tool === "copy" && tool === "copy" && Boolean(world.copyBuffer);
const result = window.TarinaiCommands?.dispatch?.(world, { type: "tool.select", toolId: tool });
const activeTool = result?.toolId || world.tool || tool;
if (activeTool !== "rope" && activeTool !== "rod") world.pendingLinkEndpoint = null;
if (activeTool !== "copy") world.copyBuffer = null;
else if (wasCopyArmed) {
world.copyBuffer = null;
showToast("\u30b3\u30d4\u30fc\u3092\u89e3\u9664\u3057\u307e\u3057\u305f\u3002");
}
for (const btn of uiCache.toolButtons) {
btn.classList.toggle("selected", btn.dataset.tool === activeTool);
}

View file

@ -1,7 +1,7 @@
"use strict";
(function () {
const APP_VERSION = "38.00.00";
const APP_VERSION = "39.00.00";
const APP_BUILD = "lab-ground-wash-and-freezer-removal-v38";
const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
const STATIC_VERSION_PARAM = `v=${APP_VERSION}`;

View file

@ -392,6 +392,7 @@
const extent = worldRef?.rotatorExtent?.(item);
if (Number.isFinite(extent)) item.r = Math.max(32, Math.min(460, extent));
item.rotatorEditorOpenAt = worldRef?.time || 0;
global.TarinaiLinkRuntime?.remapLinksForEditedMechanicalItem?.(worldRef, item, "rotator-link-remap");
worldRef?.markSpatialDirty?.("rotator-edited");
worldRef.drawListDirty = true;
worldRef?.log?.("回転体の設計を変更した。", "observe");
@ -672,6 +673,7 @@
item.reciprocatorAnchorY = item.y - Math.sin(sa) * halfTravel * (Number(item.reciprocatorPhase || 0) || 0);
const extent = worldRef?.rotatorExtent?.(item);
if (Number.isFinite(extent)) item.r = Math.max(32, Math.min(460, extent));
global.TarinaiLinkRuntime?.remapLinksForEditedMechanicalItem?.(worldRef, item, "reciprocator-link-remap");
worldRef?.markSpatialDirty?.("reciprocator-edited");
worldRef.drawListDirty = true;
worldRef?.log?.("往復体の設計を変更した。", "observe");
@ -687,11 +689,47 @@
return true;
}
function copyTargetAt(worldRef, x, y) {
let blockedReason = "";
const collision = global.TarinaiCollisionFootprints;
const items = worldRef?.items || [];
for (let i = items.length - 1; i >= 0; i--) {
const it = items[i];
if (!it || it.dead) continue;
const type = String(it.type || "");
if (type === "trace" || type === "splat" || type === "ant_corpse") continue;
if (type === "rope" || type === "rod") {
const runtime = global.TarinaiLinkRuntime;
const a = runtime?.endpointWorld?.(it.linkA, worldRef);
const b = runtime?.endpointWorld?.(it.linkB, worldRef);
const d = runtime?.pointSegmentDistance?.(x, y, a?.x ?? it.x, a?.y ?? it.y, b?.x ?? it.x, b?.y ?? it.y) ?? Infinity;
const tolerance = worldRef?.screenSizeToWorld ? worldRef.screenSizeToWorld(12) : 12;
if (d <= tolerance && !blockedReason) blockedReason = "link";
continue;
}
if (typeof isPinType === "function" && isPinType(type) && it.pinState === "lodged") {
const d = distXY(x, y, it.x, it.y);
if (d <= Math.max(24, (it.r || 12) * 1.8) && !blockedReason) blockedReason = "lodged_pin";
continue;
}
const d = distXY(x, y, it.x, it.y);
let result = null;
if (collision?.hitTestItem) {
const isPrecise = Boolean(global.TarinaiMechanicalSystem?.isMechanicalType?.(type) || worldRef?.isFenceType?.(type) || type === "nest_box");
result = collision.hitTestItem(worldRef, it, x, y, {
padding: isPrecise ? 10 : 4,
radiusMultiplier: isPrecise ? 1.12 : 1.18,
});
}
const hit = result ? Boolean(result.hit) : d <= Math.max(16, (it.r || 16) * 1.18);
if (!hit) continue;
return { item: it, reason: "" };
}
return { item: null, reason: blockedReason };
}
function copyableItemAt(worldRef, x, y) {
const it = worldRef?.findDeleteToolTargetAt?.(x, y);
if (!it || it.dead || it.type === "trace" || it.type === "splat" || it.type === "ant_corpse") return null;
if (typeof isPinType === "function" && isPinType(it.type) && it.pinState === "lodged") return null;
return it;
return copyTargetAt(worldRef, x, y).item;
}
function makeCopyBufferForItem(item) {
@ -704,17 +742,12 @@
toolSize: item.toolSize || "medium",
foodServingScale: Number(item.foodServingScale || 1) || 1,
foodServingsMax: Number(item.foodServingsMax || 0) || 0,
foodServingsRemaining: Number(item.foodServingsRemaining || 0) || 0,
seed: Number.isFinite(Number(item.seed)) ? Number(item.seed) : null,
spin: Number.isFinite(Number(item.spin)) ? Number(item.spin) : 0,
zunchiVariant: item.zunchiVariant || "",
stage: item.stage || "",
freshness: Number.isFinite(Number(item.freshness)) ? Number(item.freshness) : null,
fertility: Number.isFinite(Number(item.fertility)) ? Number(item.fertility) : null,
grassStage: Number.isFinite(Number(item.grassStage)) ? Number(item.grassStage) : null,
growth: Number.isFinite(Number(item.growth)) ? Number(item.growth) : null,
health: Number.isFinite(Number(item.health)) ? Number(item.health) : null,
fuseTimer: Number.isFinite(Number(item.fuseTimer)) ? Number(item.fuseTimer) : null,
fuseMax: Number.isFinite(Number(item.fuseMax)) ? Number(item.fuseMax) : null,
};
if (item.type === "signboard") data.text = item.text || "";
@ -747,9 +780,11 @@
if (Number.isFinite(Number(data.spin))) item.spin = Number(data.spin);
if (item.type === "zunchi") {
if (data.zunchiVariant) item.zunchiVariant = String(data.zunchiVariant);
if (data.stage) item.stage = String(data.stage);
if (Number.isFinite(Number(data.freshness))) item.freshness = Number(data.freshness);
if (Number.isFinite(Number(data.fertility))) item.fertility = Number(data.fertility);
item.stage = "fresh";
item.stageTimer = 0;
item.freshness = 1;
item.fertility = 0;
item.spawnGrace = Math.max(Number(item.spawnGrace || 0) || 0, 10);
}
if (item.type === "grass") {
if (Number.isFinite(Number(data.grassStage))) item.grassStage = Number(data.grassStage);
@ -757,8 +792,8 @@
if (Number.isFinite(Number(data.health))) item.health = Number(data.health);
}
if (item.type === "firecracker") {
if (Number.isFinite(Number(data.fuseTimer))) item.fuseTimer = Number(data.fuseTimer);
if (Number.isFinite(Number(data.fuseMax))) item.fuseMax = Number(data.fuseMax);
item.fuseTimer = Math.max(0.1, Number(item.fuseMax || 5) || 5);
}
if (typeof isRotatableItemType === "function" && isRotatableItemType(item.type)) item.angle = Number(data.angle || 0) || 0;
if (item.type === "signboard") item.text = String(data.text || "");
@ -797,13 +832,23 @@
item.toolSize = data.toolSize || item.toolSize || "medium";
item.foodServingScale = Number(data.foodServingScale || item.foodServingScale || 1) || 1;
item.foodServingsMax = Math.max(1, Number(data.foodServingsMax || item.foodServingsMax || item.amount || 1));
item.foodServingsRemaining = Math.max(1, Number(data.foodServingsRemaining || item.foodServingsRemaining || item.amount || 1));
item.foodServingsRemaining = item.foodServingsMax;
item.amount = item.foodServingsRemaining;
}
if (Number.isFinite(Number(item.x))) item.prevX = item.x;
if (Number.isFinite(Number(item.y))) item.prevY = item.y;
item.vx = 0;
item.vy = 0;
item.spinVelocity = 0;
if (typeof isPinType === "function" && isPinType(item.type)) {
item.pinState = "loose";
item.pinTargetId = "";
item.vx = 0; item.vy = 0; item.spinVelocity = 0;
item.pinAttachAngle = 0;
item.pinAttachDistance = 0;
item.pinOffsetY = 0;
item.pinDamageTick = 0;
item.pinFallCheckTimer = 0;
item.pinLogAt = -999;
}
return item;
}
@ -819,8 +864,14 @@
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; }
let d = distXY(x, y, it.x, it.y);
let hit = d <= Math.max(30, (it.r || 24) * 1.35);
if (global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type) || worldRef?.isFenceType?.(it.type)) {
const result = global.TarinaiCollisionFootprints?.hitTestItem?.(worldRef, it, x, y, { padding: 14, radiusMultiplier: 1.25 }) || { hit: false, distance: d };
hit = Boolean(result.hit);
d = Math.min(d, result.distance ?? d);
}
if (hit && d < bestD) { best = it; bestD = d; }
}
return best;
}
@ -885,9 +936,82 @@
return true;
}
function isPhysicsObstacleItemType(type = "") {
const key = String(type || "");
return Boolean(
key === "rope" || key === "rod"
|| global.TarinaiMechanicalSystem?.isMechanicalType?.(key)
|| (typeof isFenceItemType === "function" && isFenceItemType(key))
);
}
function isSoftPhysicsIgnoredItemType(type = "") {
const key = String(type || "");
return key === "water" || key === "grass_bed";
}
function softItemRadius(item) {
if (!item) return 12;
return Math.max(8, Number(item.r || item.radius || itemRadiusFor?.(item.type, 12) || 12) || 12);
}
function physicsItemOverlapsSoftItem(worldRef, physicsItem, softItem) {
if (!worldRef || !physicsItem || !softItem || softItem.dead) return false;
const r = softItemRadius(softItem);
if (physicsItem.type === "rope" || physicsItem.type === "rod") {
const runtime = global.TarinaiLinkRuntime;
const a = runtime?.endpointWorld?.(physicsItem.linkA, worldRef);
const b = runtime?.endpointWorld?.(physicsItem.linkB, worldRef);
if (a && b) {
const d = runtime?.pointSegmentDistance?.(softItem.x, softItem.y, a.x, a.y, b.x, b.y);
return Number.isFinite(d) && d <= r + (physicsItem.type === "rod" ? 9 : 7);
}
}
const rects = worldRef.solidObstacleRects?.(physicsItem) || [];
if (rects.length) {
return rects.some(rect => global.TarinaiCollisionFootprints?.rectCircleOverlap?.(rect, softItem.x, softItem.y, r, 0.5));
}
const pr = Math.max(10, Number(physicsItem.r || physicsItem.radius || itemRadiusFor?.(physicsItem.type, 12) || 12) || 12);
return distXY(physicsItem.x, physicsItem.y, softItem.x, softItem.y) <= pr + r;
}
function clearSoftItemsUnderPhysicsItem(worldRef, item, reason = "physics-overlap-clear") {
if (!worldRef || !item || !isPhysicsObstacleItemType(item.type)) return 0;
let search = Math.max(80, Number(item.r || item.radius || 36) + 90);
if (global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type)) {
search = Math.max(search, (global.TarinaiMechanicalSystem?.reach?.(item) || item.r || 80) + 96);
} else if (item.type === "rope" || item.type === "rod") {
search = Math.max(search, Number(item.linkLength || (item.r || 40) * 2 || 80) * 0.55 + 96);
}
let removed = 0;
for (const soft of worldRef.nearbyItems?.(item.x, item.y, search, true) || worldRef.items || []) {
if (!soft || soft === item || soft.dead || !isSoftPhysicsIgnoredItemType(soft.type)) continue;
if (!physicsItemOverlapsSoftItem(worldRef, item, soft)) continue;
const ok = global.TarinaiStructureLifecycle?.deleteItem?.(worldRef, soft, {
reason,
userReason: "物理アイテムに重なった",
wake: true,
});
if (ok) removed += 1;
}
if (removed) {
worldRef.markItemBucketsDirty?.(reason);
worldRef.markSpatialDirty?.(reason);
worldRef.drawListDirty = true;
}
return removed;
}
global.TarinaiPhysicsSoftClear = Object.freeze({
isPhysicsType: isPhysicsObstacleItemType,
isSoftType: isSoftPhysicsIgnoredItemType,
overlaps: physicsItemOverlapsSoftItem,
clearForItem: clearSoftItemsUnderPhysicsItem,
});
function clearOverlappingGrassBedsForPlacement(worldRef, item) {
if (!worldRef || !item || item.type === "grass_bed" || item.type === "trace" || item.type === "splat") return 0;
if (global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type) || item.type === "rope" || item.type === "rod" || (typeof isFenceItemType === "function" && isFenceItemType(item.type))) return 0;
if (isPhysicsObstacleItemType(item.type)) return clearSoftItemsUnderPhysicsItem(worldRef, item, "physics-placement-soft-clear");
const radius = Math.max(16, item.r || item.radius || 12);
let removed = 0;
for (const bed of worldRef.nearbyItems?.(item.x, item.y, radius + 72) || worldRef.items || []) {
@ -944,11 +1068,17 @@
useCopyToolAt(x, y) {
if (!this.copyBuffer) {
const target = copyableItemAt(this, x, y);
if (!target) { showToast("コピーできるものがありません。"); return true; }
const targetInfo = copyTargetAt(this, x, y);
const target = targetInfo.item;
if (!target) {
if (targetInfo.reason === "link") showToast("\u30ed\u30fc\u30d7\u3068\u68d2\u306f\u30b3\u30d4\u30fc\u3067\u304d\u307e\u305b\u3093\u3002");
else if (targetInfo.reason === "lodged_pin") showToast("\u523a\u3055\u3063\u305f\u30d4\u30f3\u306f\u30b3\u30d4\u30fc\u3067\u304d\u307e\u305b\u3093\u3002");
else showToast("\u30b3\u30d4\u30fc\u3067\u304d\u308b\u3082\u306e\u304c\u3042\u308a\u307e\u305b\u3093\u3002");
return true;
}
this.copyBuffer = makeCopyBufferForItem(target);
showToast(`${toolLabel(target.type)}をコピーしました。`);
this.log?.(`${toolLabel(target.type)}をコピーした。`, "observe");
showToast(`${toolLabel(target.type)}\u3092\u30b3\u30d4\u30fc\u3057\u307e\u3057\u305f\u3002`);
this.log?.(`${toolLabel(target.type)}\u3092\u30b3\u30d4\u30fc\u3057\u305f\u3002`, "observe");
return true;
}
const item = this.copyPreviewItemAt?.(x, y);
@ -960,17 +1090,12 @@
if (placed.type === "grass" || placed.type === "trace" || placed.type === "splat") this.markTerrainDirtyAt?.(placed.x, placed.y, Math.max(placed.r || 24, 36), "copy-paste");
forceImmediateToolVisualRefresh(this, "copy-paste");
const label = toolLabel(item.type);
this.log?.(`${label}を貼り付けた。`, "observe");
showToast(`${label}を貼り付けました。`);
this.log?.(`${label}\u3092\u8cbc\u308a\u4ed8\u3051\u305f\u3002`, "observe");
showToast(`${label}\u3092\u8cbc\u308a\u4ed8\u3051\u307e\u3057\u305f\u3002`);
return true;
}
}
const target = copyableItemAt(this, x, y);
if (target) {
this.copyBuffer = makeCopyBufferForItem(target);
showToast(`${toolLabel(target.type)}をコピーしました。`);
this.log?.(`${toolLabel(target.type)}をコピーした。`, "observe");
}
showToast("\u305d\u3053\u306b\u306f\u8cbc\u308a\u4ed8\u3051\u3067\u304d\u307e\u305b\u3093\u3002");
return true;
},
@ -1158,6 +1283,7 @@
if (item.type === "grass") showToast(`草はこのフィールドでは${this.grassLimit?.() ?? CONFIG.grassLimit ?? 99}本までです。`);
return null;
}
clearSoftItemsUnderPhysicsItem(this, item, `place:${item.type}:soft-overlap-clear`);
this.emit("tool:place", { item, type: item.type, dropped });
this.emit("tool:placed", { item, type: item.type, dropped, tool: item.type });
this.drawListDirty = true;

View file

@ -178,6 +178,7 @@
if (!hit || d >= bestD) continue;
const local = this.linkPointLocalForItem?.(it, x, y, isMechanism || isFence) || { localX: 0, localY: 0 };
best = { kind: "item", id: it.id, type: it.type, label: toolLabel(it.type), x: isMechanism || isFence ? x : it.x, y: isMechanism || isFence ? y : it.y, localX: local.localX, localY: local.localY, center: !(isMechanism || isFence) };
if (isMechanism) global.TarinaiLinkRuntime?.snapEndpointToTarget?.(best, this);
bestD = d;
}
return best;
@ -195,6 +196,8 @@
return true;
}
const first = this.pendingLinkEndpoint;
global.TarinaiLinkRuntime?.snapEndpointToTarget?.(first, this);
global.TarinaiLinkRuntime?.snapEndpointToTarget?.(endpoint, this);
if (first.kind === endpoint.kind && first.id === endpoint.id && distXY(first.x, first.y, endpoint.x, endpoint.y) < 8) {
showToast("別の位置または対象を選んでください。");
return true;
@ -217,6 +220,7 @@
const placed = this.addItem?.(item, constraintType);
this.pendingLinkEndpoint = null;
if (!placed) { showToast(`${toolName}を追加できませんでした。`); return true; }
global.TarinaiPhysicsSoftClear?.clearForItem?.(this, item, `${constraintType}-soft-overlap-clear`);
forceImmediateToolVisualRefresh(this, `${constraintType}-create`);
this.log?.(`${toolName}をつないだ。`, "observe");
showToast(`${toolName}をつなぎました。`);

View file

@ -1,6 +1,6 @@
"use strict";
const APP_VERSION = "38.00.00";
const APP_VERSION = "39.00.00";
const CACHE_NAME = `tarinai-colony-${APP_VERSION}`;
const v = `v=${APP_VERSION}`;
// Generated from app_manifest.json. Run scripts/generate_app_files.py after changing static files.