From a27fa4cd1d35bd7e36b57eb142a4f9276e7dfd52 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 22:49:36 +0300 Subject: [PATCH] feat: Add interaction hint for touch-based entity targeting --- src/react/GameInteractionOverlay.tsx | 8 +++-- src/react/InteractionHint.module.css | 19 ++++++++++ src/react/InteractionHint.module.css.d.ts | 10 ++++++ src/react/InteractionHint.tsx | 44 +++++++++++++++++++++++ src/reactUi.tsx | 2 ++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/react/InteractionHint.module.css create mode 100644 src/react/InteractionHint.module.css.d.ts create mode 100644 src/react/InteractionHint.tsx diff --git a/src/react/GameInteractionOverlay.tsx b/src/react/GameInteractionOverlay.tsx index cb7a39f8..7c406e8d 100644 --- a/src/react/GameInteractionOverlay.tsx +++ b/src/react/GameInteractionOverlay.tsx @@ -150,9 +150,13 @@ function GameInteractionOverlayInner ({ document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) virtualClickActive = false } else if (!capturedPointer.active.activateCameraMove && (Date.now() - capturedPointer.active.time < touchStartBreakingBlockMs)) { - document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) + // single click action + const MOUSE_BUTTON_RIGHT = 2 + const MOUSE_BUTTON_LEFT = 0 + const gonnaAttack = !!bot.mouse.getCursorState().entity + document.dispatchEvent(new MouseEvent('mousedown', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT })) bot.mouse.update() - document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) + document.dispatchEvent(new MouseEvent('mouseup', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT })) } if (screenTouches > 0) { diff --git a/src/react/InteractionHint.module.css b/src/react/InteractionHint.module.css new file mode 100644 index 00000000..026b49c8 --- /dev/null +++ b/src/react/InteractionHint.module.css @@ -0,0 +1,19 @@ +.hint_container { + position: fixed; + top: 20%; + left: 0; + right: 0; + margin: 0 auto; + width: fit-content; + display: flex; + align-items: center; + gap: 8px; + pointer-events: none; + z-index: 1000; + text-shadow: 1px 1px 8px rgba(0, 0, 0, 1); +} + +.hint_text { + color: white; + font-size: 10px; +} diff --git a/src/react/InteractionHint.module.css.d.ts b/src/react/InteractionHint.module.css.d.ts new file mode 100644 index 00000000..45bdf30b --- /dev/null +++ b/src/react/InteractionHint.module.css.d.ts @@ -0,0 +1,10 @@ +// This file is automatically generated. +// Please do not change this file! +interface CssExports { + hintContainer: string; + hintText: string; + hint_container: string; + hint_text: string; +} +declare const cssExports: CssExports; +export default cssExports; diff --git a/src/react/InteractionHint.tsx b/src/react/InteractionHint.tsx new file mode 100644 index 00000000..9121249e --- /dev/null +++ b/src/react/InteractionHint.tsx @@ -0,0 +1,44 @@ +import { useEffect, useState } from 'react' +import { useSnapshot } from 'valtio' +import { options } from '../optionsStorage' +import PixelartIcon, { pixelartIcons } from './PixelartIcon' +import styles from './InteractionHint.module.css' +import { useUsingTouch } from './utilsApp' + +export default () => { + const usingTouch = useUsingTouch() + const { touchInteractionType } = useSnapshot(options) + const [hintText, setHintText] = useState(null) + + useEffect(() => { + const update = () => { + const cursorState = bot.mouse.getCursorState() + if (cursorState.entity) { + const entityName = cursorState.entity.displayName ?? cursorState.entity.name + setHintText(`Attack ${entityName}`) + } else { + setHintText(null) + } + } + + // Initial update + update() + + // Subscribe to physics ticks + bot.on('physicsTick', update) + + return () => { + bot.removeListener('physicsTick', update) + } + }, []) + + if (!usingTouch || touchInteractionType !== 'classic') return null + if (!hintText) return null + + return ( +
+ + {hintText} +
+ ) +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index ac2dbe74..f07c01ce 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -52,6 +52,7 @@ import MineflayerPluginConsole from './react/MineflayerPluginConsole' import { UIProvider } from './react/UIProvider' import { useAppScale } from './scaleInterface' import PacketsReplayProvider from './react/PacketsReplayProvider' +import InteractionHint from './react/InteractionHint' const RobustPortal = ({ children, to }) => { return createPortal({children}, to) @@ -146,6 +147,7 @@ const InGameUi = () => { + {showUI && }
{!disabledUiParts.includes('xp-bar') && } {!disabledUiParts.includes('hud-bars') && }