Skip to content

Commit edfa21d

Browse files
committed
feat: 연속 실패 카운터 + 이중강화 방어 강화 + 오버레이 개선 (v2.10.0)
- 연속 유지(hold) 카운터 도입: 5회 이상 시 경고 + 딜레이 동적 증가 - 클립보드 잔여물 근본 제거 (ClearClipboard 추가) - 레벨 점프 이상 감지 로깅 - OCR/입력 영역 오버레이 배경 제거 (테두리만 표시)
1 parent 555d6a1 commit edfa21d

File tree

5 files changed

+90
-16
lines changed

5 files changed

+90
-16
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.9.0"
22+
const VERSION = "2.10.0"
2323

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

internal/config/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type Config struct {
3434
OverlayChatHeight int `json:"overlay_chat_height"` // 채팅 영역 높이
3535
OverlayInputWidth int `json:"overlay_input_width"` // 입력 영역 너비
3636
OverlayInputHeight int `json:"overlay_input_height"` // 입력 영역 높이
37+
38+
// 연속 실패 경고 임계값 (hold 연속 N회 시 경고, 기본 5)
39+
ConsecutiveFailWarn int `json:"consecutive_fail_warn"`
3740
}
3841

3942
// Default 기본 설정 반환
@@ -55,6 +58,8 @@ func Default() *Config {
5558
OverlayChatHeight: 430,
5659
OverlayInputWidth: 380,
5760
OverlayInputHeight: 50,
61+
// 연속 실패 경고
62+
ConsecutiveFailWarn: 5,
5863
}
5964
}
6065

internal/game/engine.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ type Engine struct {
6565
// 이전 RAW 텍스트 (응답 변경 감지용)
6666
lastRawChatText string
6767

68+
// 연속 실패 추적 (getDelayForLevel 공유용)
69+
enhanceConsecutiveFails int // 현재 강화 루프의 연속 실패 횟수
70+
6871
// 세션 통계 (종료 시 출력용)
6972
sessionStats struct {
7073
startGold int
@@ -76,6 +79,8 @@ type Engine struct {
7679
enhanceDestroy int
7780
cycleTimeSum float64 // 사이클 시간 합계 (초)
7881
cycleGoldSum int // 사이클 수익 합계
82+
consecutiveFails int // 현재 연속 실패(유지) 횟수
83+
maxConsecutiveFails int // 세션 최대 연속 실패 횟수
7984
}
8085
}
8186

@@ -653,6 +658,9 @@ func (e *Engine) printSessionStats() {
653658
fmt.Printf(" ✅ 강화 성공: %d회\n", e.sessionStats.enhanceSuccess)
654659
fmt.Printf(" ⏸️ 강화 유지: %d회\n", e.sessionStats.enhanceHold)
655660
fmt.Printf(" 💥 강화 파괴: %d회\n", e.sessionStats.enhanceDestroy)
661+
if e.sessionStats.maxConsecutiveFails > 0 {
662+
fmt.Printf(" 🔥 최대 연속유지: %d회\n", e.sessionStats.maxConsecutiveFails)
663+
}
656664
}
657665

658666
// 배틀 통계
@@ -791,6 +799,8 @@ func (e *Engine) loopEnhance() {
791799
switch state.LastResult {
792800
case "destroy":
793801
e.sessionStats.enhanceDestroy++
802+
e.sessionStats.consecutiveFails = 0
803+
e.enhanceConsecutiveFails = 0
794804
e.telem.RecordEnhanceWithType(itemType, currentLevel, "destroy")
795805
fmt.Printf(" 💥 +%d에서 파괴!\n", currentLevel)
796806
overlay.UpdateStatus("⚔️ 강화 중\n💥 +%d 파괴!\n\n📋 판단: 새 검으로 재시작", currentLevel)
@@ -805,22 +815,41 @@ func (e *Engine) loopEnhance() {
805815

806816
case "success":
807817
e.sessionStats.enhanceSuccess++
818+
e.sessionStats.consecutiveFails = 0
819+
e.enhanceConsecutiveFails = 0
820+
prevLevel := currentLevel
808821
if state.ResultLevel > 0 {
809822
currentLevel = state.ResultLevel
810823
} else {
811824
currentLevel++
812825
}
826+
// 이중 강화 감지: 레벨이 2 이상 점프하면 의심스러움
827+
if currentLevel > prevLevel+1 {
828+
logger.Error("의심스러운 레벨 점프: +%d → +%d (예상: +%d)", prevLevel, currentLevel, prevLevel+1)
829+
}
813830
e.telem.RecordEnhanceWithType(itemType, currentLevel-1, "success")
814831
fmt.Printf(" ⚔️ 강화 성공! +%d\n", currentLevel)
815832
overlay.UpdateStatus("⚔️ 강화 중\n현재: +%d → 목표: +%d\n\n📋 판단: 성공!", currentLevel, e.targetLevel)
816833

817834
case "hold":
818835
e.sessionStats.enhanceHold++
836+
e.sessionStats.consecutiveFails++
837+
e.enhanceConsecutiveFails = e.sessionStats.consecutiveFails
838+
if e.sessionStats.consecutiveFails > e.sessionStats.maxConsecutiveFails {
839+
e.sessionStats.maxConsecutiveFails = e.sessionStats.consecutiveFails
840+
}
819841
if state.ResultLevel > 0 && state.ResultLevel != currentLevel {
820842
currentLevel = state.ResultLevel
821843
}
822844
e.telem.RecordEnhanceWithType(itemType, currentLevel, "hold")
823-
fmt.Printf(" 💫 +%d 유지\n", currentLevel)
845+
// 연속 실패 경고
846+
if e.cfg.ConsecutiveFailWarn > 0 && e.sessionStats.consecutiveFails >= e.cfg.ConsecutiveFailWarn {
847+
fmt.Printf(" ⚠️ +%d 유지 (연속 %d회!)\n", currentLevel, e.sessionStats.consecutiveFails)
848+
overlay.UpdateStatus("⚔️ 강화 중\n⚠️ 연속 유지 %d회!\n+%d → +%d",
849+
e.sessionStats.consecutiveFails, currentLevel, e.targetLevel)
850+
} else {
851+
fmt.Printf(" 💫 +%d 유지\n", currentLevel)
852+
}
824853

825854
default:
826855
// 결과 불명확 — ResultLevel로 동기화 시도
@@ -1626,6 +1655,10 @@ func (e *Engine) readChatClipboard() string {
16261655
// 채팅 영역에서 텍스트 읽기 (전체선택 → 복사 → 클립보드)
16271656
text := input.ReadChatText(chatClickX, chatClickY, inputX, inputY)
16281657

1658+
// 클립보드 잔여물 근본 제거: ReadChatText 후 클립보드를 비워서
1659+
// 다음 Cmd+C 실패 시 이전 텍스트(명령어)가 남아있지 않도록 함
1660+
input.ClearClipboard()
1661+
16291662
// 클립보드 잔여물 감지: sendCommand의 TypeText가 Cmd+V용으로 클립보드에
16301663
// 명령어 텍스트("/강화", "/판매" 등)를 남김. ReadChatText의 Cmd+A→Cmd+C가
16311664
// 간헐적으로 실패하면 이전 명령어가 클립보드에 남아있게 됨.
@@ -2142,6 +2175,16 @@ func (e *Engine) getDelayForLevel(level int) time.Duration {
21422175
default:
21432176
delay = e.cfg.HighDelay
21442177
}
2178+
2179+
// 연속 실패 시 딜레이 증가 (5회 이상부터 10%씩, 최대 50%)
2180+
if e.enhanceConsecutiveFails >= 5 {
2181+
mult := 1.0 + float64(e.enhanceConsecutiveFails-4)*0.1
2182+
if mult > 1.5 {
2183+
mult = 1.5
2184+
}
2185+
delay *= mult
2186+
}
2187+
21452188
return time.Duration(delay * float64(time.Second))
21462189
}
21472190

internal/game/helpers.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package game
33
import (
44
"fmt"
55
"time"
6+
7+
"github.com/StopDragon/sword-macro-ai/internal/logger"
68
)
79

810
// =============================================================================
@@ -89,11 +91,12 @@ func (e *Engine) LogProfileStatus(profile ProfileCheckResult, modePrefix string)
8991

9092
// EnhanceResult 강화 진행 결과
9193
type EnhanceResult struct {
92-
FinalLevel int
93-
Success bool // 목표 도달 여부
94-
Destroyed bool // 파괴 여부
95-
NewSwordName string // 파괴 시 새로 받은 검 이름
96-
NewSwordType string // 파괴 시 새로 받은 검 타입
94+
FinalLevel int
95+
Success bool // 목표 도달 여부
96+
Destroyed bool // 파괴 여부
97+
NewSwordName string // 파괴 시 새로 받은 검 이름
98+
NewSwordType string // 파괴 시 새로 받은 검 타입
99+
MaxConsecutiveFails int // 이 강화의 최대 연속 실패(유지) 횟수
97100
}
98101

99102
// EnhanceToTarget 목표 레벨까지 강화 진행 (시작 레벨 지정 가능)
@@ -103,9 +106,13 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
103106
// 타입 기반 강화 통계용 (normal/special/trash)
104107
itemType := DetermineItemType(itemName)
105108

109+
// 연속 실패 추적 (로컬)
110+
consecutiveFails := 0
111+
maxConsecutiveFails := 0
112+
106113
for currentLevel < e.targetLevel && e.running {
107114
if e.checkStop() {
108-
return EnhanceResult{FinalLevel: currentLevel, Success: false, Destroyed: false}
115+
return EnhanceResult{FinalLevel: currentLevel, Success: false, Destroyed: false, MaxConsecutiveFails: maxConsecutiveFails}
109116
}
110117

111118
// 강화 시도
@@ -132,8 +139,9 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
132139
if state.LastResult == "destroy" {
133140
// 타입+레벨별 강화 통계 기록
134141
e.telem.RecordEnhanceWithType(itemType, currentLevel, "destroy")
142+
e.enhanceConsecutiveFails = 0
135143

136-
result := EnhanceResult{FinalLevel: currentLevel, Success: false, Destroyed: true}
144+
result := EnhanceResult{FinalLevel: currentLevel, Success: false, Destroyed: true, MaxConsecutiveFails: maxConsecutiveFails}
137145

138146
// 파괴 시 새 검 정보 추출
139147
// 1순위: 파괴 전용 패턴 "『[+0] 낡은 검』 지급되었습니다"
@@ -156,7 +164,10 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
156164
// 핵심: ResultLevel("+X → +Y" 패턴에서 추출)이 가장 정확함
157165

158166
if state.LastResult == "success" {
167+
consecutiveFails = 0
168+
e.enhanceConsecutiveFails = 0
159169
// 강화 성공 - ResultLevel 우선 사용 (가장 정확함)
170+
prevLevel := currentLevel
160171
if state.ResultLevel > 0 {
161172
currentLevel = state.ResultLevel
162173
fmt.Printf(" ⚔️ 강화 성공! +%d 도달\n", currentLevel)
@@ -165,16 +176,30 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
165176
currentLevel++
166177
fmt.Printf(" ⚔️ 강화 성공! +%d 도달 (계산값)\n", currentLevel)
167178
}
179+
// 이중 강화 감지: 레벨이 2 이상 점프하면 의심스러움
180+
if currentLevel > prevLevel+1 {
181+
logger.Error("의심스러운 레벨 점프 (EnhanceToTarget): +%d → +%d (예상: +%d)", prevLevel, currentLevel, prevLevel+1)
182+
}
168183
// 타입+레벨별 강화 통계 기록 (강화 전 레벨 기준)
169184
e.telem.RecordEnhanceWithType(itemType, currentLevel-1, "success")
170185
} else if state.LastResult == "hold" {
186+
consecutiveFails++
187+
e.enhanceConsecutiveFails = consecutiveFails
188+
if consecutiveFails > maxConsecutiveFails {
189+
maxConsecutiveFails = consecutiveFails
190+
}
171191
// 유지 시에도 ResultLevel 확인 (현재 레벨 동기화)
172192
// 채팅에 성공(+9→+10)과 유지(+10)가 동시에 잡힐 때
173193
// LastResult="hold"가 되지만 ResultLevel은 정확히 10을 가리킴
174194
if state.ResultLevel > 0 && state.ResultLevel != currentLevel {
175195
currentLevel = state.ResultLevel
176196
}
177-
fmt.Printf(" 💫 강화 유지 (현재 +%d)\n", currentLevel)
197+
// 연속 실패 경고
198+
if e.cfg.ConsecutiveFailWarn > 0 && consecutiveFails >= e.cfg.ConsecutiveFailWarn {
199+
fmt.Printf(" ⚠️ +%d 유지 (연속 %d회!)\n", currentLevel, consecutiveFails)
200+
} else {
201+
fmt.Printf(" 💫 강화 유지 (현재 +%d)\n", currentLevel)
202+
}
178203
// 타입+레벨별 강화 통계 기록
179204
e.telem.RecordEnhanceWithType(itemType, currentLevel, "hold")
180205
} else if state.LastResult == "destroy" {
@@ -193,7 +218,7 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
193218
if goldInfo.IsInsufficient {
194219
fmt.Printf("⚠️ 골드 부족! 필요: %s, 보유: %s\n",
195220
FormatGold(goldInfo.RequiredGold), FormatGold(goldInfo.RemainingGold))
196-
return EnhanceResult{FinalLevel: currentLevel, Success: false, Destroyed: false}
221+
return EnhanceResult{FinalLevel: currentLevel, Success: false, Destroyed: false, MaxConsecutiveFails: maxConsecutiveFails}
197222
}
198223

199224
}
@@ -206,9 +231,10 @@ func (e *Engine) EnhanceToTarget(itemName string, startLevel int) EnhanceResult
206231
}
207232

208233
return EnhanceResult{
209-
FinalLevel: currentLevel,
210-
Success: currentLevel >= e.targetLevel,
211-
Destroyed: false,
234+
FinalLevel: currentLevel,
235+
Success: currentLevel >= e.targetLevel,
236+
Destroyed: false,
237+
MaxConsecutiveFails: maxConsecutiveFails,
212238
}
213239
}
214240

internal/overlay/overlay_darwin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void ShowOCRRegion(int x, int y, int width, int height) {
5555
contentView.wantsLayer = YES;
5656
contentView.layer.borderColor = [[NSColor redColor] CGColor];
5757
contentView.layer.borderWidth = 3.0;
58-
contentView.layer.backgroundColor = [[NSColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.1] CGColor];
58+
contentView.layer.backgroundColor = [[NSColor clearColor] CGColor];
5959
6060
[ocrWindow setContentView:contentView];
6161
[ocrWindow orderFrontRegardless];
@@ -88,7 +88,7 @@ void ShowInputRegion(int x, int y, int width, int height) {
8888
contentView.wantsLayer = YES;
8989
contentView.layer.borderColor = [[NSColor greenColor] CGColor];
9090
contentView.layer.borderWidth = 3.0;
91-
contentView.layer.backgroundColor = [[NSColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:0.1] CGColor];
91+
contentView.layer.backgroundColor = [[NSColor clearColor] CGColor];
9292
9393
[inputWindow setContentView:contentView];
9494
[inputWindow orderFrontRegardless];

0 commit comments

Comments
 (0)