Skip to content

Commit 401d7f6

Browse files
committed
fix: 배틀 모드 프로필 인식 및 0강 감지 버그 수정 (v2.7.1)
- extractProfileSection: 첫 번째 프로필 대신 마지막(최신) 프로필 반환하도록 수정 - 상대방 0강 배틀 에러 감지 및 후보 목록에서 자동 제거 - 프로필 조회 시 SaveLastChatText로 변경하여 응답 감지 안정화 - 클립보드 오염 방지를 위한 ClearClipboard 추가
1 parent 6364644 commit 401d7f6

File tree

7 files changed

+83
-22
lines changed

7 files changed

+83
-22
lines changed

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.7.0"
22+
const VERSION = "2.7.1"
2323

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

internal/game/engine.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ func (e *Engine) setupAndRun() {
351351
// 프로필 가져오기
352352
fmt.Println("📊 프로필 확인 중...")
353353
overlay.UpdateStatus("📊 프로필 확인 중...")
354+
// 카카오톡 포커스 확보 (카운트다운 중 터미널에 포커스 있을 수 있음)
355+
input.Click(e.cfg.ClickX, e.cfg.ClickY)
356+
time.Sleep(300 * time.Millisecond)
357+
e.SaveLastChatText()
354358
e.sendCommand("/프로필")
355359

356360
profileText := e.waitForResponse(10 * time.Second)
@@ -1225,7 +1229,11 @@ func (e *Engine) loopBattle() {
12251229
FormatGold(e.totalGold), winRate, e.battleWins, e.battleLosses)
12261230

12271231
e.SaveLastChatText()
1228-
e.sendCommand("/배틀" + target.Username)
1232+
// 배틀 명령어는 다단계로 전송 (카카오톡 인식 안정성)
1233+
// /배틀 → 0.3초 → 엔터(줄바꿈) → 0.3초 → @이름 → 엔터,엔터(전송)
1234+
e.sendCommandOnce("/배틀")
1235+
time.Sleep(300 * time.Millisecond)
1236+
e.appendAndSend(target.Username)
12291237
// 배틀 결과는 상대 이름 포함 → filterMyMessages가 패배 결과를 제거할 수 있으므로 Raw 사용
12301238
resultText := e.waitForResponseRaw(5 * time.Second)
12311239

@@ -1247,6 +1255,20 @@ func (e *Engine) loopBattle() {
12471255
continue
12481256
}
12491257

1258+
// 상대방 0강 감지 → 해당 타겟 제거 후 다음 타겟으로
1259+
if DetectBattleZeroLevel(resultText) {
1260+
fmt.Printf(" ⚠️ %s: 상대 검이 0강 → 타겟에서 제거\n", target.Username)
1261+
// candidates에서 해당 타겟 제거
1262+
for i, c := range candidates {
1263+
if c.Username == target.Username {
1264+
candidates = append(candidates[:i], candidates[i+1:]...)
1265+
break
1266+
}
1267+
}
1268+
time.Sleep(1 * time.Second)
1269+
continue
1270+
}
1271+
12501272
// 배틀 횟수 제한 확인 (하루 10회 제한)
12511273
if DetectBattleLimit(resultText) {
12521274
// 최종 승률 계산

internal/game/helpers.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ type ProfileCheckResult struct {
2121
// CheckProfileLevel 프로필에서 현재 레벨과 검 이름 조회
2222
// loopEnhance에서 추출한 공통 로직
2323
func (e *Engine) CheckProfileLevel() ProfileCheckResult {
24-
// 이전 채팅 기록 초기화하여응답 감지 보장
25-
e.ResetLastChatText()
24+
// 현재 채팅 저장 후응답만 감지
25+
e.SaveLastChatText()
2626

2727
e.sendCommand("/프로필")
2828
profileText := e.waitForResponse(5 * time.Second)
@@ -297,8 +297,8 @@ func (e *Engine) LogProfileCheck(profile ProfileCheckResult) {
297297
// CheckProfileFull 전체 프로필 정보 조회 (Profile 구조체 반환)
298298
// loopBattle, showMyProfile 등에서 전체 프로필이 필요할 때 사용
299299
func (e *Engine) CheckProfileFull() *Profile {
300-
// 이전 채팅 기록 초기화하여응답 감지 보장
301-
e.ResetLastChatText()
300+
// 현재 채팅 저장 후응답만 감지
301+
e.SaveLastChatText()
302302

303303
e.sendCommand("/프로필")
304304
profileText := e.waitForResponse(5 * time.Second)
@@ -326,6 +326,7 @@ func (e *Engine) CheckOtherProfile(username string) *Profile {
326326

327327
// 1단계: /프로 + Enter(줄바꿈만)
328328
e.sendCommandOnce("/프로")
329+
time.Sleep(300 * time.Millisecond)
329330

330331
// 2단계: @유저명 + Enter 2번(전송)
331332
e.appendAndSend(username)

internal/game/parser.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ var (
119119
battleVsPattern = regexp.MustCompile(`(@\S+)\s*『\[([^\]]+)\]`)
120120
// 배틀 횟수 제한 패턴 (하루 10회 제한 도달 시)
121121
// 〖🚫 배틀 횟수 제한〗 또는 "오늘은 이미 10번의 배틀"
122-
battleLimitPattern = regexp.MustCompile(`(?:배틀\s*횟수\s*제한|오늘.*10번.*배틀|오늘\s*배틀.*모두\s*사용)`)
122+
battleLimitPattern = regexp.MustCompile(`(?:배틀\s*횟수\s*제한|오늘.*10번.*배틀|오늘\s*배틀.*모두\s*사용)`)
123+
battleZeroLevelPattern = regexp.MustCompile(`(?:0강이라네|0강하고\s*배틀|아직\s*0강)`)
123124

124125
// 함수 내부에서 사용하는 정규식 (매번 컴파일 방지)
125126
acquiredSwordLevelPattern = regexp.MustCompile(`획득\s*검:\s*\[\+?(\d+)\]`)
@@ -601,19 +602,21 @@ func ParseProfileForUser(text string, username string) *Profile {
601602

602603
// extractProfileSection 특정 유저의 프로필 섹션 추출
603604
// ⚔️ [프로필] 다음에 ● 이름: @유저명 이 있는 섹션만 추출
605+
// 채팅 히스토리에 같은 유저의 프로필이 여러 번 있을 수 있으므로 마지막(최신) 프로필 반환
604606
func extractProfileSection(text string, username string) string {
605607
lines := strings.Split(text, "\n")
606608
var section []string
609+
var lastMatchedSection []string
607610
foundProfileHeader := false
608611
foundTargetUser := false
609612

610613
for _, line := range lines {
611614
// 프로필 헤더 감지: ⚔️ [프로필]
612615
if strings.Contains(line, "[프로필]") {
613-
// 새 프로필 시작 - 이전 섹션 리셋
616+
// 새 프로필 시작 - 이전 타겟 유저 섹션 저장
614617
if foundTargetUser {
615-
// 이미 타겟 유저 찾았으면 여기서 종료
616-
break
618+
lastMatchedSection = section
619+
foundTargetUser = false
617620
}
618621
section = []string{line}
619622
foundProfileHeader = true
@@ -640,10 +643,15 @@ func extractProfileSection(text string, username string) string {
640643
}
641644
}
642645

643-
if !foundTargetUser || len(section) == 0 {
646+
// 마지막으로 찾은 섹션이 타겟 유저면 그것을 사용
647+
if foundTargetUser {
648+
lastMatchedSection = section
649+
}
650+
651+
if lastMatchedSection == nil || len(lastMatchedSection) == 0 {
644652
return ""
645653
}
646-
return strings.Join(section, "\n")
654+
return strings.Join(lastMatchedSection, "\n")
647655
}
648656

649657
// ParseProfile 프로필 파싱
@@ -836,6 +844,12 @@ func DetectBattleLimit(text string) bool {
836844
return battleLimitPattern.MatchString(text)
837845
}
838846

847+
// DetectBattleZeroLevel 상대방 검이 0강인 경우 감지
848+
// "자네가 지목한 상대의 검은 아직 0강이라네" 등의 메시지
849+
func DetectBattleZeroLevel(text string) bool {
850+
return battleZeroLevelPattern.MatchString(text)
851+
}
852+
839853
// FindTargetsInRanking 랭킹에서 역배 타겟 찾기
840854
func FindTargetsInRanking(entries []RankingEntry, myLevel int, levelDiff int) []RankingEntry {
841855
var targets []RankingEntry

internal/input/input.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,30 @@ func GetClipboard() string {
4848
return getClipboard()
4949
}
5050

51+
// ClearClipboard 클립보드 비우기 (복사 실패 시 이전 내용 반환 방지)
52+
func ClearClipboard() {
53+
clearClipboard()
54+
}
55+
5156
// ReadChatText 채팅 영역에서 텍스트 읽기 (클립보드 복사 방식)
5257
// chatX, chatY: 채팅 영역 클릭 좌표
5358
// inputX, inputY: 입력창 좌표 (복귀용)
5459
func ReadChatText(chatX, chatY, inputX, inputY int) string {
60+
// 0. 클립보드 비우기 (이전 TypeText 등으로 오염된 클립보드 방지)
61+
// Copy 실패 시 이전 명령어 텍스트가 반환되는 것을 차단
62+
ClearClipboard()
63+
5564
// 1. 채팅 영역 클릭 (텍스트 선택 가능하도록)
5665
Click(chatX, chatY)
57-
time.Sleep(35 * time.Millisecond)
66+
time.Sleep(50 * time.Millisecond)
5867

5968
// 2. 전체 선택 (Cmd+A / Ctrl+A)
6069
SelectAll()
61-
time.Sleep(35 * time.Millisecond)
70+
time.Sleep(50 * time.Millisecond)
6271

6372
// 3. 복사 (Cmd+C / Ctrl+C)
6473
CopySelection()
65-
time.Sleep(70 * time.Millisecond)
74+
time.Sleep(100 * time.Millisecond)
6675

6776
// 4. 클립보드에서 텍스트 가져오기
6877
text := GetClipboard()
@@ -82,15 +91,15 @@ func SendCommand(x, y int, command string) {
8291

8392
// 2. 입력창 청소 (Cmd+A → Delete)
8493
ClearInput()
85-
time.Sleep(30 * time.Millisecond)
94+
time.Sleep(60 * time.Millisecond)
8695

8796
// 3. 텍스트 입력 (클립보드 + Cmd+V)
8897
TypeText(command)
89-
time.Sleep(100 * time.Millisecond)
98+
time.Sleep(150 * time.Millisecond)
9099

91100
// 4. 엔터 2번 (줄바꿈 + 전송)
92101
PressEnter()
93-
time.Sleep(150 * time.Millisecond)
102+
time.Sleep(200 * time.Millisecond)
94103
PressEnter()
95104
time.Sleep(50 * time.Millisecond)
96105
}
@@ -104,11 +113,11 @@ func SendCommandOnce(x, y int, command string) {
104113

105114
// 2. 입력창 청소 (Cmd+A → Delete)
106115
ClearInput()
107-
time.Sleep(30 * time.Millisecond)
116+
time.Sleep(60 * time.Millisecond)
108117

109118
// 3. 텍스트 입력 (클립보드 + Cmd+V)
110119
TypeText(command)
111-
time.Sleep(100 * time.Millisecond)
120+
time.Sleep(150 * time.Millisecond)
112121

113122
// 4. 엔터 1번만 (줄바꿈)
114123
PressEnter()
@@ -122,11 +131,11 @@ func SendCommandOnce(x, y int, command string) {
122131
func AppendAndSend(x, y int, text string) {
123132
// 클릭 없이 바로 텍스트 추가 (이전 단계에서 커서가 이미 끝에 있음)
124133
TypeText(text)
125-
time.Sleep(100 * time.Millisecond)
134+
time.Sleep(150 * time.Millisecond)
126135

127136
// 엔터 2번 (전송)
128137
PressEnter()
129-
time.Sleep(150 * time.Millisecond)
138+
time.Sleep(200 * time.Millisecond)
130139
PressEnter()
131140
time.Sleep(50 * time.Millisecond)
132141
}

internal/input/input_darwin.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,9 @@ func getClipboard() string {
172172
C.free(unsafe.Pointer(cStr))
173173
return str
174174
}
175+
176+
func clearClipboard() {
177+
cText := C.CString("")
178+
defer C.free(unsafe.Pointer(cText))
179+
C.setClipboard(cText)
180+
}

internal/input/input_windows.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,3 +242,12 @@ func getClipboard() string {
242242
utf16Slice := (*[1 << 20]uint16)(unsafe.Pointer(pMem))[:size/2:size/2]
243243
return syscall.UTF16ToString(utf16Slice)
244244
}
245+
246+
func clearClipboard() {
247+
ret, _, _ := openClipboard.Call(0)
248+
if ret == 0 {
249+
return
250+
}
251+
emptyClipboard.Call()
252+
closeClipboard.Call()
253+
}

0 commit comments

Comments
 (0)