Skip to content

Commit fe722e4

Browse files
StopDragonclaude
andcommitted
Enhance overlay with multi-region display and real-time status
- Add three separate overlay windows: OCR region (red), input region (green), status panel - Show real-time decision logs with OCR results, state, and actions - Keep overlay visible during entire macro execution - Add status updates for farming, enhancing, and selling phases - Update both macOS (Cocoa) and Windows (Win32 API) implementations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 56b3f97 commit fe722e4

File tree

3 files changed

+397
-116
lines changed

3 files changed

+397
-116
lines changed

internal/game/engine.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,24 +198,37 @@ func (e *Engine) setupAndRun() {
198198
fmt.Printf("좌표 저장됨: (%d, %d)\n", e.cfg.ClickX, e.cfg.ClickY)
199199
}
200200

201-
// OCR 캡처 영역 표시
201+
// OCR 캡처 영역 및 입력창 영역 계산
202202
captureX := e.cfg.ClickX - e.cfg.CaptureW/2
203203
captureY := e.cfg.ClickY - e.cfg.InputBoxH/2 - e.cfg.CaptureH
204+
inputX := e.cfg.ClickX - 150 // 입력창 너비 추정
205+
inputY := e.cfg.ClickY - e.cfg.InputBoxH/2
206+
inputW := 300
207+
inputH := e.cfg.InputBoxH
208+
204209
fmt.Println()
205-
fmt.Println("🔴 빨간색 테두리가 OCR 캡처 영역입니다!")
206-
overlay.Show(captureX, captureY, e.cfg.CaptureW, e.cfg.CaptureH)
207-
fmt.Printf(" 위치: (%d, %d) ~ (%d, %d)\n", captureX, captureY, captureX+e.cfg.CaptureW, captureY+e.cfg.CaptureH)
208-
fmt.Println("⚠️ 카카오톡 채팅창을 빨간 테두리 안에 맞춰 배치하세요!")
210+
fmt.Println("🔴 빨간 테두리 = OCR 캡처 영역 (채팅 내용)")
211+
fmt.Println("🟢 초록 테두리 = 입력창 영역 (명령어 입력)")
212+
fmt.Printf(" OCR: (%d, %d) ~ (%d, %d)\n", captureX, captureY, captureX+e.cfg.CaptureW, captureY+e.cfg.CaptureH)
213+
fmt.Printf(" 입력: (%d, %d) ~ (%d, %d)\n", inputX, inputY, inputX+inputW, inputY+inputH)
214+
215+
// 모든 오버레이 표시 (OCR 영역 + 입력창 영역 + 상태 패널)
216+
overlay.ShowAll(captureX, captureY, e.cfg.CaptureW, e.cfg.CaptureH, inputX, inputY, inputW, inputH)
217+
overlay.UpdateStatus("🎮 준비 중...\n카카오톡 창을\n오버레이에 맞춰주세요")
218+
219+
fmt.Println()
220+
fmt.Println("⚠️ 카카오톡 채팅창을 오버레이에 맞춰 배치하세요!")
209221
fmt.Println()
210222

211223
// 5초 대기
212224
fmt.Print("⏳ 준비 대기: ")
213225
for i := 5; i > 0; i-- {
214226
fmt.Printf("%d... ", i)
227+
overlay.UpdateStatus("🎮 준비 중... %d초\n카카오톡 창을\n오버레이에 맞춰주세요", i)
215228
time.Sleep(1 * time.Second)
216229
}
217230
fmt.Println("시작!")
218-
overlay.Hide()
231+
overlay.UpdateStatus("🚀 시작!")
219232

220233
// OCR 초기화
221234
fmt.Println("OCR 엔진 초기화 중...")
@@ -265,6 +278,11 @@ func (e *Engine) setupAndRun() {
265278
e.loopBattle()
266279
}
267280

281+
// 종료 시 오버레이 숨기기
282+
overlay.UpdateStatus("⏹️ 종료 중...")
283+
time.Sleep(500 * time.Millisecond)
284+
overlay.HideAll()
285+
268286
// 종료 시 통계 출력 및 텔레메트리 전송
269287
elapsed := time.Since(e.startTime)
270288
fmt.Println()
@@ -368,19 +386,23 @@ func (e *Engine) loopGoldMine() {
368386
// v2: 세션 초기화
369387
startGold := e.readCurrentGold()
370388
e.telem.InitSession(startGold)
389+
overlay.UpdateStatus("💰 골드 채굴 모드\n사이클: 0\n수익: 0G")
371390

372391
for e.running {
373392
e.cycleStartTime = time.Now()
374393
e.cycleCount++
375394

376395
// 1. 파밍 (아이템 이름 반환)
396+
overlay.UpdateStatus("💰 골드 채굴 #%d\n🔍 파밍 중...\n누적: %sG", e.cycleCount, FormatGold(e.totalGold))
377397
itemName, found := e.farmUntilHiddenWithName()
378398
if !found {
379399
e.telem.RecordCycle(false)
400+
overlay.UpdateStatus("💰 골드 채굴 #%d\n❌ 파밍 실패\n누적: %sG", e.cycleCount, FormatGold(e.totalGold))
380401
continue
381402
}
382403

383404
// 2. 강화
405+
overlay.UpdateStatus("💰 골드 채굴 #%d\n⚔️ 강화 중: %s\n누적: %sG", e.cycleCount, itemName, FormatGold(e.totalGold))
384406
cycleStartGold := e.readCurrentGold()
385407
finalLevel, success := e.enhanceToTargetWithLevel()
386408
if !success {
@@ -389,6 +411,7 @@ func (e *Engine) loopGoldMine() {
389411
}
390412

391413
// 3. 판매
414+
overlay.UpdateStatus("💰 골드 채굴 #%d\n💵 판매 중: %s +%d\n누적: %sG", e.cycleCount, itemName, finalLevel, FormatGold(e.totalGold))
392415
e.sendCommand("/판매")
393416
time.Sleep(500 * time.Millisecond)
394417

@@ -405,6 +428,9 @@ func (e *Engine) loopGoldMine() {
405428
e.telem.RecordGoldChange(endGold)
406429
e.telem.TrySend()
407430

431+
// 사이클 완료 상태 업데이트
432+
overlay.UpdateStatus("💰 골드 채굴 #%d ✅\n%s +%d → %+sG\n누적: %sG", e.cycleCount, itemName, finalLevel, FormatGold(goldEarned), FormatGold(e.totalGold))
433+
408434
fmt.Printf("📦 사이클 #%d: %.1f초, %+dG | 누적: %dG [%s +%d]\n",
409435
e.cycleCount, cycleTime.Seconds(), goldEarned, e.totalGold, itemName, finalLevel)
410436
}

internal/overlay/overlay_darwin.go

Lines changed: 171 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,52 +8,144 @@ package overlay
88
99
#import <Cocoa/Cocoa.h>
1010
11-
static NSWindow *overlayWindow = nil;
11+
static NSWindow *ocrWindow = nil;
12+
static NSWindow *inputWindow = nil;
13+
static NSWindow *statusWindow = nil;
14+
static NSTextField *statusLabel = nil;
1215
13-
void ShowOverlay(int x, int y, int width, int height) {
16+
// OCR 영역 오버레이 (빨간색)
17+
void ShowOCRRegion(int x, int y, int width, int height) {
1418
dispatch_async(dispatch_get_main_queue(), ^{
15-
if (overlayWindow != nil) {
16-
[overlayWindow close];
17-
overlayWindow = nil;
19+
if (ocrWindow != nil) {
20+
[ocrWindow close];
21+
ocrWindow = nil;
1822
}
1923
2024
NSRect frame = NSMakeRect(x, [[NSScreen mainScreen] frame].size.height - y - height, width, height);
2125
22-
overlayWindow = [[NSWindow alloc]
26+
ocrWindow = [[NSWindow alloc]
2327
initWithContentRect:frame
2428
styleMask:NSWindowStyleMaskBorderless
2529
backing:NSBackingStoreBuffered
2630
defer:NO];
2731
28-
[overlayWindow setLevel:NSFloatingWindowLevel];
29-
[overlayWindow setBackgroundColor:[NSColor clearColor]];
30-
[overlayWindow setOpaque:NO];
31-
[overlayWindow setIgnoresMouseEvents:YES];
32-
[overlayWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
32+
[ocrWindow setLevel:NSFloatingWindowLevel];
33+
[ocrWindow setBackgroundColor:[NSColor clearColor]];
34+
[ocrWindow setOpaque:NO];
35+
[ocrWindow setIgnoresMouseEvents:YES];
36+
[ocrWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
3337
34-
// 빨간색 테두리 뷰 생성
3538
NSView *contentView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
3639
contentView.wantsLayer = YES;
3740
contentView.layer.borderColor = [[NSColor redColor] CGColor];
38-
contentView.layer.borderWidth = 3.0;
39-
contentView.layer.backgroundColor = [[NSColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.1] CGColor];
41+
contentView.layer.borderWidth = 2.0;
42+
contentView.layer.backgroundColor = [[NSColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.05] CGColor];
4043
41-
[overlayWindow setContentView:contentView];
42-
[overlayWindow makeKeyAndOrderFront:nil];
44+
[ocrWindow setContentView:contentView];
45+
[ocrWindow makeKeyAndOrderFront:nil];
4346
});
4447
}
4548
46-
void HideOverlay() {
49+
// 입력창 영역 오버레이 (초록색)
50+
void ShowInputRegion(int x, int y, int width, int height) {
4751
dispatch_async(dispatch_get_main_queue(), ^{
48-
if (overlayWindow != nil) {
49-
[overlayWindow close];
50-
overlayWindow = nil;
52+
if (inputWindow != nil) {
53+
[inputWindow close];
54+
inputWindow = nil;
55+
}
56+
57+
NSRect frame = NSMakeRect(x, [[NSScreen mainScreen] frame].size.height - y - height, width, height);
58+
59+
inputWindow = [[NSWindow alloc]
60+
initWithContentRect:frame
61+
styleMask:NSWindowStyleMaskBorderless
62+
backing:NSBackingStoreBuffered
63+
defer:NO];
64+
65+
[inputWindow setLevel:NSFloatingWindowLevel];
66+
[inputWindow setBackgroundColor:[NSColor clearColor]];
67+
[inputWindow setOpaque:NO];
68+
[inputWindow setIgnoresMouseEvents:YES];
69+
[inputWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
70+
71+
NSView *contentView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
72+
contentView.wantsLayer = YES;
73+
contentView.layer.borderColor = [[NSColor greenColor] CGColor];
74+
contentView.layer.borderWidth = 2.0;
75+
contentView.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:0.05] CGColor];
76+
77+
[inputWindow setContentView:contentView];
78+
[inputWindow makeKeyAndOrderFront:nil];
79+
});
80+
}
81+
82+
// 상태 패널 (우측 하단)
83+
void ShowStatusPanel(int x, int y, int width, int height) {
84+
dispatch_async(dispatch_get_main_queue(), ^{
85+
if (statusWindow != nil) {
86+
[statusWindow close];
87+
statusWindow = nil;
88+
statusLabel = nil;
89+
}
90+
91+
NSRect frame = NSMakeRect(x, [[NSScreen mainScreen] frame].size.height - y - height, width, height);
92+
93+
statusWindow = [[NSWindow alloc]
94+
initWithContentRect:frame
95+
styleMask:NSWindowStyleMaskBorderless
96+
backing:NSBackingStoreBuffered
97+
defer:NO];
98+
99+
[statusWindow setLevel:NSFloatingWindowLevel];
100+
[statusWindow setBackgroundColor:[NSColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8]];
101+
[statusWindow setOpaque:NO];
102+
[statusWindow setIgnoresMouseEvents:YES];
103+
[statusWindow setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces];
104+
105+
statusLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, width - 20, height - 20)];
106+
[statusLabel setBezeled:NO];
107+
[statusLabel setDrawsBackground:NO];
108+
[statusLabel setEditable:NO];
109+
[statusLabel setSelectable:NO];
110+
[statusLabel setTextColor:[NSColor whiteColor]];
111+
[statusLabel setFont:[NSFont monospacedSystemFontOfSize:11 weight:NSFontWeightRegular]];
112+
[statusLabel setStringValue:@"🎮 대기 중..."];
113+
114+
[[statusWindow contentView] addSubview:statusLabel];
115+
[statusWindow makeKeyAndOrderFront:nil];
116+
});
117+
}
118+
119+
// 상태 텍스트 업데이트
120+
void UpdateStatus(const char *text) {
121+
NSString *nsText = [NSString stringWithUTF8String:text];
122+
dispatch_async(dispatch_get_main_queue(), ^{
123+
if (statusLabel != nil) {
124+
[statusLabel setStringValue:nsText];
125+
}
126+
});
127+
}
128+
129+
// 모든 오버레이 숨기기
130+
void HideAllOverlays() {
131+
dispatch_async(dispatch_get_main_queue(), ^{
132+
if (ocrWindow != nil) {
133+
[ocrWindow close];
134+
ocrWindow = nil;
135+
}
136+
if (inputWindow != nil) {
137+
[inputWindow close];
138+
inputWindow = nil;
139+
}
140+
if (statusWindow != nil) {
141+
[statusWindow close];
142+
statusWindow = nil;
143+
statusLabel = nil;
51144
}
52145
});
53146
}
54147
55148
void InitApp() {
56-
// NSApplication 초기화 (메인 스레드에서)
57149
dispatch_async(dispatch_get_main_queue(), ^{
58150
[NSApplication sharedApplication];
59151
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
@@ -62,6 +154,7 @@ void InitApp() {
62154
*/
63155
import "C"
64156
import (
157+
"fmt"
65158
"time"
66159
)
67160

@@ -76,17 +169,69 @@ func Init() {
76169
}
77170
}
78171

79-
// Show OCR 캡처 영역 오버레이 표시
172+
// Show OCR 캡처 영역 오버레이 표시 (하위 호환)
80173
func Show(x, y, width, height int) {
174+
ShowOCRRegion(x, y, width, height)
175+
}
176+
177+
// Hide 오버레이 숨기기 (하위 호환)
178+
func Hide() {
179+
HideAll()
180+
}
181+
182+
// ShowOCRRegion OCR 영역 표시 (빨간색)
183+
func ShowOCRRegion(x, y, width, height int) {
81184
if !initialized {
82185
Init()
83186
}
84-
C.ShowOverlay(C.int(x), C.int(y), C.int(width), C.int(height))
187+
C.ShowOCRRegion(C.int(x), C.int(y), C.int(width), C.int(height))
85188
}
86189

87-
// Hide 오버레이 숨기기
88-
func Hide() {
89-
C.HideOverlay()
190+
// ShowInputRegion 입력창 영역 표시 (초록색)
191+
func ShowInputRegion(x, y, width, height int) {
192+
if !initialized {
193+
Init()
194+
}
195+
C.ShowInputRegion(C.int(x), C.int(y), C.int(width), C.int(height))
196+
}
197+
198+
// ShowStatusPanel 상태 패널 표시
199+
func ShowStatusPanel(x, y, width, height int) {
200+
if !initialized {
201+
Init()
202+
}
203+
C.ShowStatusPanel(C.int(x), C.int(y), C.int(width), C.int(height))
204+
}
205+
206+
// ShowAll 모든 오버레이 표시 (OCR 영역, 입력창 영역, 상태 패널)
207+
func ShowAll(ocrX, ocrY, ocrW, ocrH, inputX, inputY, inputW, inputH int) {
208+
if !initialized {
209+
Init()
210+
}
211+
212+
// OCR 영역 (빨간색)
213+
ShowOCRRegion(ocrX, ocrY, ocrW, ocrH)
214+
215+
// 입력창 영역 (초록색)
216+
ShowInputRegion(inputX, inputY, inputW, inputH)
217+
218+
// 상태 패널 (OCR 영역 오른쪽)
219+
statusX := ocrX + ocrW + 10
220+
statusY := ocrY
221+
statusW := 280
222+
statusH := 150
223+
ShowStatusPanel(statusX, statusY, statusW, statusH)
224+
}
225+
226+
// UpdateStatus 상태 텍스트 업데이트
227+
func UpdateStatus(format string, args ...interface{}) {
228+
text := fmt.Sprintf(format, args...)
229+
C.UpdateStatus(C.CString(text))
230+
}
231+
232+
// HideAll 모든 오버레이 숨기기
233+
func HideAll() {
234+
C.HideAllOverlays()
90235
}
91236

92237
// ShowForDuration 지정 시간 동안 오버레이 표시

0 commit comments

Comments
 (0)