Skip to content

Commit 62d3bf5

Browse files
committed
fix: 강화 응답 대기 로직 및 입력 속도 최적화 (v2.5.5)
- 모든 모드에서 /강화 후 게임 응답 대기 추가 (중복 전송 방지) - 유지/불명확 결과에서도 ResultLevel 동기화 (목표 미감지 수정) - filterMyMessages 상태 머신 방식 개선 (다른 유저 결과 혼입 방지) - 입력/읽기 딜레이 최적화 (SendCommand ~46%, ReadChatText ~42% 절감) - README 전면 개편
1 parent 6e543bf commit 62d3bf5

File tree

6 files changed

+168
-116
lines changed

6 files changed

+168
-116
lines changed

README.md

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@
22

33
카카오톡 **검키우기** 게임 자동화 매크로
44

5-
> Go 네이티브 빌드 — 바이너리 2.3MB, 외부 의존성 없음
5+
## 왜 이 매크로인가?
6+
7+
일반적인 매크로는 단순히 클릭을 반복합니다.
8+
**sword-macro-ai**는 다릅니다.
9+
10+
| | 일반 매크로 | sword-macro-ai |
11+
|--|------------|----------------|
12+
| 강화 방식 | 무작정 클릭 반복 | 빅데이터 기반 최적 타이밍 분석 |
13+
| 판매 전략 | 고정 레벨에서 판매 | 레벨별 기대 수익 계산 후 최적 시점 판매 |
14+
| 역배 대결 | 랜덤 상대 선택 | 레벨 차이별 승률·보상 데이터 분석 |
15+
| 리스크 관리 | 없음 | 파산 확률, 최대 손실폭 실시간 계산 |
16+
| 데이터 활용 | 없음 | 커뮤니티 전체 통계 기반 의사결정 |
17+
18+
수천 건의 실제 플레이 데이터를 분석하여 **강화 확률, 판매 적정가, 역배 기대값**을 실시간으로 계산합니다. 감이 아닌 데이터로 플레이합니다.
619

720
## 다운로드
821

@@ -29,102 +42,99 @@
2942

3043
</details>
3144

32-
## 기능
33-
34-
| 모드 | 설명 |
35-
|------|------|
36-
| 강화 목표 달성 | 설정한 레벨까지 자동 강화 |
37-
| 특수 아이템 뽑기 | 특수 아이템 발견까지 파밍 |
38-
| 골드 채굴 | 파밍 → 강화 → 판매 무한 반복 |
39-
| 자동 배틀 (역배) | 높은 레벨 상대와 자동 대결 |
40-
| 내 프로필 분석 | 검 정보, 판매가, 강화확률, 역배 기대값 분석 |
41-
42-
**조작**: F8 일시정지, F9 재시작, 마우스 좌상단 = 비상정지
43-
4445
## 사용법
4546

4647
1. 카카오톡에서 검키우기 채팅방 열기
4748
2. 매크로 실행 → 모드 선택
4849
3. 카카오톡 **메시지 입력칸 '메시지 입력'**에 마우스 올리기 → 3초 후 좌표 자동 저장
49-
4. 매크로 시작
50+
4. 매크로 자동 시작
5051

51-
## 설정
52+
## 모드 소개
5253

53-
| 항목 | 기본값 | 설명 |
54-
|------|--------|------|
55-
| 감속 시작 레벨 | +9 | 고강 딜레이 시작점 |
56-
| 중간 속도 | 2.5초 | +5~+8 강화 대기 |
57-
| 고강 속도 | 3.5초 | +9~ 강화 대기 |
58-
| 좌표 고정 | OFF | 저장된 좌표 재사용 |
54+
### ⚔️ 강화 목표 달성
55+
56+
원하는 강화 레벨을 설정하면 자동으로 강화합니다.
5957

60-
설정 파일: `sword_config.json`
58+
- 목표 레벨(+1 ~ +20)을 선택하면 자동으로 달성
59+
- 골드 부족 시 자동으로 파밍 후 재시도
60+
- 고강(+9 이상)에서는 자동으로 속도 조절하여 안정적 강화
6161

62-
## 빌드
62+
### 🎁 특수 아이템 뽑기
6363

64-
```bash
65-
git clone https://github.com/StopDragon/sword-macro-ai.git
66-
cd sword-macro-ai
64+
특수 아이템이 나올 때까지 자동으로 파밍합니다.
6765

68-
# 클라이언트 (매크로)
69-
make build-mac # macOS
70-
make build-mac-universal # macOS Universal (Intel + Apple Silicon)
71-
make build-windows # Windows
66+
- 특수 아이템 발견 시 자동 알림 및 정지
67+
- 원하면 특수 아이템을 목표 레벨까지 자동 강화
68+
- 일반 아이템은 자동 처리하여 골드 확보
7269

73-
# API 서버
74-
make build-api # 현재 OS용
75-
make build-api-linux # Linux (서버 배포용)
76-
```
70+
### 💰 골드 채굴 (돈벌기)
7771

78-
**요구사항**: Go 1.21+, macOS 12+ / Windows 10+
72+
**파밍 → 강화 → 판매** 사이클을 자동으로 무한 반복합니다.
7973

80-
## API 서버
74+
- 설정한 목표 레벨까지 강화 후 자동 판매
75+
- 빅데이터 분석 기반으로 **가장 수익률이 높은 판매 시점**을 적용
76+
- 특수 아이템 발견 시 자동 보관
77+
- 실시간으로 획득 골드, 사이클 수, 성공률 표시
8178

82-
게임 데이터(강화 확률, 판매가, 역배 보상)를 제공하는 API 서버입니다.
79+
### ⚡ 자동 배틀 (역배)
8380

84-
```bash
85-
# 로컬 실행
86-
make run-api
81+
나보다 높은 레벨의 상대와 자동으로 대결합니다.
8782

88-
# 또는
89-
PORT=8080 go run ./cmd/sword-api
90-
```
83+
- 레벨 차이(1~3단계) 설정 가능
84+
- **레벨 차이별 승률과 보상 데이터를 분석**하여 최적의 상대 선택
85+
- 하루 10회 제한 자동 관리
9186

92-
**엔드포인트**:
93-
- `GET /api/game-data` - 게임 데이터 조회
94-
- `POST /api/telemetry` - 텔레메트리 수신
95-
- `GET /api/stats/detailed` - 커뮤니티 통계
87+
### 📊 내 프로필 분석
9688

97-
## 기술
89+
내 검 상태를 데이터로 분석합니다. 실행하면 다음 정보를 한눈에 확인할 수 있습니다.
9890

99-
| | macOS | Windows |
100-
|--|-------|---------|
101-
| 캡처 | ScreenCaptureKit | GDI32 |
102-
| OCR | Vision Framework | Windows.Media.Ocr |
103-
| 입력 | CGEvent | SendInput |
91+
| 분석 항목 | 설명 |
92+
|-----------|------|
93+
| 내 검 정보 | 현재 검 이름, 강화 레벨, 보유 골드, 배틀 전적 |
94+
| 예상 판매가 | 현재 레벨 기준 최소/평균/최대 판매 가격 |
95+
| 강화 확률표 | 현재 레벨부터 +20까지 성공/유지/파괴 확률 |
96+
| 목표 달성 확률 | 원하는 레벨까지 도달할 확률과 예상 시도 횟수 |
97+
| 역배 분석 | 레벨 차이별 기대 수익과 추천 전략 |
98+
99+
## 조작법
100+
101+
|| 기능 |
102+
|----|------|
103+
| F8 | 일시정지 |
104+
| F9 | 재시작 |
105+
106+
## 설정
107+
108+
매크로 실행 후 **옵션 설정** 메뉴에서 변경할 수 있습니다.
109+
110+
| 항목 | 기본값 | 설명 |
111+
|------|--------|------|
112+
| 감속 시작 레벨 | +9 | 이 레벨부터 강화 속도를 늦춰 안정성 확보 |
113+
| 중간 속도 | 2.5초 | +5~+8 강화 시 대기 시간 |
114+
| 고강 속도 | 3.5초 | +9 이상 강화 시 대기 시간 |
115+
| 좌표 고정 | OFF | 저장된 클릭 좌표를 재사용 |
116+
| 골드 채굴 목표 | +10 | 골드 채굴 시 판매 전 강화 목표 레벨 |
117+
| 역배 레벨 차이 | 1 | 배틀 상대와의 레벨 차이 (1~3) |
118+
119+
설정은 자동으로 `sword_config.json`에 저장됩니다.
104120

105121
## 데이터 수집 안내
106122

107123
이 매크로는 서비스 개선을 위해 **익명화된 사용 통계**를 수집합니다.
108124

109-
### 수집 항목
125+
**수집하는 것**
110126
- 강화 시도/성공/실패/파괴 횟수
111127
- 배틀 횟수 및 승패
112128
- 파밍/판매 통계
113129
- 앱 버전, OS 종류
114130

115-
### 수집하지 않는 항목
131+
**절대 수집하지 않는 **
116132
- 개인 식별 정보 (IP, 계정명 등)
117133
- 카카오톡 대화 내용
118134
- 화면 캡처 이미지
119135
- 키보드/마우스 입력 원본
120136

121-
### 보안
122-
- 모든 통신은 HTTPS로 암호화
123-
- 세션 ID는 SHA-256 해시로 익명화
124-
- 텔레메트리 전송 시 서명 검증으로 위변조 방지
125-
- 수집된 데이터는 통계 목적으로만 사용
126-
127-
수집된 통계는 `/api/stats/detailed` 엔드포인트에서 누구나 확인 가능합니다.
137+
모든 통신은 HTTPS로 암호화되며, 세션 ID는 SHA-256 해시로 익명화됩니다. 수집된 데이터는 강화 확률, 판매가, 역배 보상 등의 **커뮤니티 통계에만 사용**됩니다.
128138

129139
## License
130140

cmd/sword-macro/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func init() {
1919
runtime.LockOSThread()
2020
}
2121

22-
const VERSION = "2.5.3"
22+
const VERSION = "2.5.5"
2323

2424
func main() {
2525
// Windows 콘솔 ANSI 지원 활성화 및 UTF-8 설정

internal/game/engine.go

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -612,16 +612,41 @@ func (e *Engine) loopEnhance() {
612612
delay := e.getDelayForLevel(currentLevel)
613613
time.Sleep(delay)
614614

615-
// 결과 확인 및 골드 부족 체크
616-
text := e.readChatText()
615+
// 결과 확인 - 게임 응답이 올 때까지 대기
616+
text := e.readChatTextWaitForChange(5 * time.Second)
617+
618+
// 응답이 없으면 재시도 (게임 응답 전에 읽은 경우)
619+
if text == "" {
620+
for retry := 0; retry < 3 && e.running; retry++ {
621+
time.Sleep(1 * time.Second)
622+
text = e.readChatTextWaitForChange(3 * time.Second)
623+
if text != "" {
624+
break
625+
}
626+
}
627+
}
628+
617629
if text != "" {
630+
// 골드 부족 체크
618631
goldInfo := DetectInsufficientGold(text)
619632
if goldInfo.IsInsufficient {
620633
overlay.UpdateStatus("⚔️ 강화 중단\n💰 골드 부족!\n필요: %s\n보유: %s",
621634
FormatGold(goldInfo.RequiredGold), FormatGold(goldInfo.RemainingGold))
622635
e.handleInsufficientGold(goldInfo)
623636
return
624637
}
638+
639+
// 강화 결과에서 레벨 확인하여 목표 달성 시 즉시 종료
640+
if resultState := ParseOCRText(text); resultState != nil {
641+
resultLevel := e.ExtractCurrentLevel(resultState)
642+
if e.IsTargetReached(resultLevel) {
643+
fmt.Printf("\n🎉 목표 달성! +%d\n", resultLevel)
644+
logger.Info("목표 달성: +%d", resultLevel)
645+
overlay.UpdateStatus("⚔️ 강화 완료!\n🎉 +%d 달성!\n\n📋 판단: 목표 도달 → 완료", resultLevel)
646+
e.ReportSwordComplete()
647+
return
648+
}
649+
}
625650
}
626651
}
627652
}
@@ -1419,9 +1444,11 @@ func (e *Engine) waitForResponseInternal(maxWait time.Duration, raw bool) string
14191444
return ""
14201445
}
14211446

1422-
// filterMyMessages 내 메시지만 필터링 (가장 최근 @이름 섹션만)
1423-
// 다른 유저의 강화 결과가 섞이는 것을 방지
1424-
// 예: "플레이봇 @권혁진 〖💦강화 유지💦〗" + "[+12] 검이름" 이 내 결과에 혼입되는 문제 차단
1447+
// filterMyMessages 내 메시지만 필터링 (다른 유저 영역 제거 방식)
1448+
// 기존 "마지막 섹션 선택" 방식의 문제:
1449+
// 같은 채팅창에 성공(+9→+10)과 유지(+10)가 동시에 잡힐 때
1450+
// 마지막 @myName(유지)만 반환 → 성공 결과 유실 → 목표 도달 감지 실패
1451+
// 개선: 다른 유저의 영역만 제거하고, 내 메시지는 모두 보존
14251452
func (e *Engine) filterMyMessages(text string) string {
14261453
if e.sessionProfile == nil || e.sessionProfile.Name == "" {
14271454
return text // 프로필 없으면 전체 반환
@@ -1430,33 +1457,33 @@ func (e *Engine) filterMyMessages(text string) string {
14301457
myName := e.sessionProfile.Name // "@행복사랑평화" 형식
14311458
lines := strings.Split(text, "\n")
14321459

1433-
// 가장 마지막 내 메시지 섹션의 시작점 찾기
1434-
// "플레이봇 @내이름" 패턴 (게임봇이 나에게 보낸 결과)
1435-
lastMyIndex := -1
1436-
for i, line := range lines {
1437-
if strings.Contains(line, myName) {
1438-
lastMyIndex = i // 마지막 내 섹션 시작점 갱신
1439-
}
1440-
}
1441-
1442-
// 내 섹션이 없으면 전체 반환
1443-
if lastMyIndex == -1 {
1444-
return text
1445-
}
1446-
1447-
// 마지막 내 섹션부터 끝까지 또는 다른 사람 섹션 시작 전까지
1460+
// 다른 유저 영역 제거, 내 영역은 모두 보존
1461+
// 상태 머신: @가 포함된 줄에서 유저 전환 감지
1462+
// - @myName 포함 → 내 영역 (포함)
1463+
// - @있지만 myName 없음 → 다른 유저 영역 (제거)
1464+
// - @없음 → 현재 상태 유지 (이전 영역에 속하는 상세 메시지)
14481465
var result []string
1449-
for i := lastMyIndex; i < len(lines); i++ {
1450-
line := lines[i]
1466+
inOtherSection := false
14511467

1452-
// 다른 유저의 게임 메시지가 시작되면 중단
1453-
// @가 포함되어 있지만 내 이름(@myName)이 없는 줄 = 다른 유저의 영역
1454-
// 예: "12:21 플레이봇 @권혁진 〖결과〗" 또는 "12:21 권혁진 @플레이봇 강화"
1455-
if i > lastMyIndex && strings.Contains(line, "@") && !strings.Contains(line, myName) {
1456-
break
1468+
for _, line := range lines {
1469+
hasAt := strings.Contains(line, "@")
1470+
hasMy := strings.Contains(line, myName)
1471+
1472+
if hasAt {
1473+
if hasMy {
1474+
// 내 영역으로 전환 (결과, 속보 등)
1475+
inOtherSection = false
1476+
} else {
1477+
// 다른 유저 영역으로 전환
1478+
// 예: "플레이봇 @권혁진 〖결과〗", "한지원 @플레이봇 강화"
1479+
inOtherSection = true
1480+
continue // 이 줄도 제거
1481+
}
14571482
}
14581483

1459-
result = append(result, line)
1484+
if !inOtherSection {
1485+
result = append(result, line)
1486+
}
14601487
}
14611488

14621489
if len(result) == 0 {
@@ -1544,8 +1571,8 @@ func (e *Engine) farmForGoldMine() (string, string, int, bool) {
15441571
e.sendCommand("/강화")
15451572
time.Sleep(time.Duration(e.cfg.TrashDelay * float64(time.Second)))
15461573

1547-
// 강화 결과 읽기
1548-
enhanceText := e.readChatText()
1574+
// 강화 결과 읽기 (응답 대기)
1575+
enhanceText := e.readChatTextWaitForChange(5 * time.Second)
15491576
enhanceState := ParseOCRText(enhanceText)
15501577

15511578
if enhanceState != nil {

internal/game/helpers.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,17 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
111111
delay := e.getDelayForLevel(currentLevel)
112112
time.Sleep(delay)
113113

114-
// 결과 확인
114+
// 결과 확인 - 게임 응답이 올 때까지 대기
115+
// 내 명령만 보이고 게임 응답(성공/유지/파괴)이 없으면 재읽기
115116
text := e.readChatTextWaitForChange(5 * time.Second)
116117
state := ParseOCRText(text)
117118

119+
for retry := 0; retry < 3 && state.LastResult == "" && e.running; retry++ {
120+
time.Sleep(1 * time.Second)
121+
text = e.readChatTextWaitForChange(3 * time.Second)
122+
state = ParseOCRText(text)
123+
}
124+
118125
if state == nil {
119126
continue
120127
}
@@ -154,13 +161,21 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
154161
fmt.Printf(" ⚔️ 강화 성공! +%d 도달 (계산값)\n", currentLevel)
155162
}
156163
} else if state.LastResult == "hold" {
157-
// 유지 = 레벨 변화 없음
164+
// 유지 시에도 ResultLevel 확인 (현재 레벨 동기화)
165+
// 채팅에 성공(+9→+10)과 유지(+10)가 동시에 잡힐 때
166+
// LastResult="hold"가 되지만 ResultLevel은 정확히 10을 가리킴
167+
if state.ResultLevel > 0 && state.ResultLevel != currentLevel {
168+
currentLevel = state.ResultLevel
169+
}
158170
fmt.Printf(" 💫 강화 유지 (현재 +%d)\n", currentLevel)
159171
} else if state.LastResult == "destroy" {
160172
// 파괴는 위에서 처리됨, 여기 오면 안됨
161173
fmt.Printf(" [경고] destroy가 fallback에서 감지됨\n")
162174
} else {
163-
// 결과 불명확 - 레벨 변경 없이 재시도
175+
// 결과 불명확 - ResultLevel이라도 확인하여 레벨 동기화
176+
if state.ResultLevel > 0 && state.ResultLevel != currentLevel {
177+
currentLevel = state.ResultLevel
178+
}
164179
fmt.Printf(" ❓ 결과 불명확 (LastResult='%s') - 재시도\n", state.LastResult)
165180
}
166181

0 commit comments

Comments
 (0)