@@ -554,28 +554,26 @@ func (e *Engine) printSessionStats() {
554554}
555555
556556func (e * Engine ) loopEnhance () {
557- // 1. 시작 전 프로필 체크 (공통 헬퍼 사용)
558- overlay .UpdateStatus ("⚔️ 강화 모드\n 목표: +%d\n \n 📋 프로필 확인 중..." , e .targetLevel )
559- fmt .Println ("📊 현재 검 상태 확인 중..." )
560-
561- profile := e .CheckProfileLevel ()
557+ // 시작 시 프로필 정보 표시 (Run()에서 이미 조회한 sessionProfile 사용)
558+ // 중복 /프로필 전송 방지
559+ overlay .UpdateStatus ("⚔️ 강화 모드\n 목표: +%d" , e .targetLevel )
562560
563- if profile . OK {
564- fmt .Printf ("📋 현재 보유 검: [+%d] %s\n " , profile . Level , profile .SwordName )
561+ if e . sessionProfile != nil && e . sessionProfile . SwordName != "" {
562+ fmt .Printf ("📋 현재 보유 검: [+%d] %s\n " , e . sessionProfile . Level , e . sessionProfile .SwordName )
565563
566- // 2. 이미 목표 달성한 경우 종료 (공통 헬퍼 사용)
567- if e .IsTargetReached (profile .Level ) {
568- fmt .Printf ("\n ✅ 이미 목표 달성! 현재 +%d (목표: +%d)\n " , profile .Level , e .targetLevel )
564+ // 이미 목표 달성한 경우 종료
565+ if e .IsTargetReached (e . sessionProfile .Level ) {
566+ fmt .Printf ("\n ✅ 이미 목표 달성! 현재 +%d (목표: +%d)\n " , e . sessionProfile .Level , e .targetLevel )
569567 fmt .Println ("💡 강화할 필요가 없습니다. 메뉴로 돌아갑니다." )
570- overlay .UpdateStatus ("⚔️ 강화 불필요\n ✅ 이미 +%d 보유!\n 목표: +%d\n \n 📋 판단: 목표 이미 달성" , profile .Level , e .targetLevel )
568+ overlay .UpdateStatus ("⚔️ 강화 불필요\n ✅ 이미 +%d 보유!\n 목표: +%d\n \n 📋 판단: 목표 이미 달성" , e . sessionProfile .Level , e .targetLevel )
571569 time .Sleep (2 * time .Second )
572570 return
573571 }
574572
575573 // 현재 레벨이 0보다 크면 기존 검으로 계속 강화
576- if profile .Level > 0 {
577- fmt .Printf ("📈 현재 +%d에서 목표 +%d까지 강화를 시작합니다.\n " , profile .Level , e .targetLevel )
578- overlay .UpdateStatus ("⚔️ 강화 모드\n 현재: +%d → 목표: +%d\n [%s]\n \n 📋 판단: 기존 검 강화 계속" , profile . Level , e .targetLevel , profile .SwordName )
574+ if e . sessionProfile .Level > 0 {
575+ fmt .Printf ("📈 현재 +%d에서 목표 +%d까지 강화를 시작합니다.\n " , e . sessionProfile .Level , e .targetLevel )
576+ overlay .UpdateStatus ("⚔️ 강화 모드\n 현재: +%d → 목표: +%d\n [%s]\n \n 📋 판단: 기존 검 강화 계속" , e . sessionProfile . Level , e .targetLevel , e . sessionProfile .SwordName )
579577 } else {
580578 fmt .Printf ("📈 +0에서 목표 +%d까지 강화를 시작합니다.\n " , e .targetLevel )
581579 overlay .UpdateStatus ("⚔️ 강화 모드\n 현재: +0 → 목표: +%d\n \n 📋 판단: 새 검 강화 시작" , e .targetLevel )
@@ -817,16 +815,26 @@ func (e *Engine) loopGoldMine() {
817815 e .telem .InitSession (startGold )
818816 overlay .UpdateStatus ("💰 골드 채굴 모드\n 목표: +%d\n 사이클: 0\n 수익: 0G" , e .targetLevel )
819817
820- // 시작 시 프로필 체크 (공통 헬퍼 사용 - loopEnhance와 동일)
821- fmt .Println ("📊 현재 검 상태 확인 중..." )
822- profile := e .CheckProfileLevel ()
823- if profile .OK {
824- fmt .Printf ("📋 현재 보유 검: [+%d] %s\n " , profile .Level , profile .SwordName )
818+ // 시작 시 프로필 정보 표시 (Run()에서 이미 조회한 sessionProfile 사용)
819+ // 중복 /프로필 전송 방지
820+ if e .sessionProfile != nil && e .sessionProfile .SwordName != "" {
821+ fmt .Printf ("📋 현재 보유 검: [+%d] %s\n " , e .sessionProfile .Level , e .sessionProfile .SwordName )
822+
823+ // 아이템 타입 확인
824+ itemType := DetermineItemType (e .sessionProfile .SwordName )
825+ fmt .Printf (" 아이템 타입: %s\n " , GetItemTypeLabel (itemType ))
826+
827+ // 이미 목표 달성한 경우 바로 판매 (일반 아이템만)
828+ if e .IsTargetReached (e .sessionProfile .Level ) {
829+ if itemType == "special" {
830+ fmt .Printf ("✅ 목표 달성! 특수 아이템 [%s] +%d → 보관\n " , e .sessionProfile .SwordName , e .sessionProfile .Level )
831+ overlay .UpdateStatus ("💰 골드 채굴\n ✅ 특수 +%d 보관!" , e .sessionProfile .Level )
832+ e .telem .TrySend ()
833+ return // 특수 아이템은 판매하지 않음
834+ }
825835
826- // 이미 목표 달성한 경우 바로 판매
827- if e .IsTargetReached (profile .Level ) {
828- fmt .Printf ("✅ 이미 목표 달성! 현재 +%d → 바로 판매\n " , profile .Level )
829- overlay .UpdateStatus ("💰 골드 채굴\n ✅ 이미 +%d 보유!\n 💵 판매 진행" , profile .Level )
836+ fmt .Printf ("✅ 이미 목표 달성! 현재 +%d → 바로 판매\n " , e .sessionProfile .Level )
837+ overlay .UpdateStatus ("💰 골드 채굴\n ✅ 이미 +%d 보유!\n 💵 판매 진행" , e .sessionProfile .Level )
830838 e .sendCommand ("/판매" )
831839 saleText := e .readChatTextWaitForChange (5 * time .Second )
832840 saleResult := ExtractSaleResult (saleText )
@@ -999,15 +1007,17 @@ func (e *Engine) loopGoldMine() {
9991007
10001008func (e * Engine ) loopBattle () {
10011009 fmt .Println ()
1002- fmt .Println ("📊 프로필 확인 중..." )
10031010
1004- // 1. 내 프로필 확인 (공통 헬퍼 사용)
1005- e . myProfile = e . CheckProfileFull ()
1006- if e .myProfile == nil || e .myProfile .Level < 0 {
1011+ // 시작 시 프로필 정보 표시 (Run()에서 이미 조회한 sessionProfile 사용)
1012+ // 중복 /프로필 전송 방지
1013+ if e .sessionProfile == nil || e .sessionProfile .Level < 0 {
10071014 fmt .Println ("❌ 프로필을 읽을 수 없습니다. 다시 시도하세요." )
10081015 return
10091016 }
10101017
1018+ // 배틀 모드에서 사용할 myProfile에 sessionProfile 복사
1019+ e .myProfile = e .sessionProfile
1020+
10111021 fmt .Printf ("📋 내 프로필: +%d %s (%d승 %d패)\n " ,
10121022 e .myProfile .Level , e .myProfile .SwordName , e .myProfile .Wins , e .myProfile .Losses )
10131023 fmt .Printf ("🎯 타겟 범위: +%d ~ +%d\n " ,
@@ -1018,6 +1028,9 @@ func (e *Engine) loopBattle() {
10181028 startGold := e .readCurrentGold ()
10191029 e .telem .InitSession (startGold )
10201030
1031+ // 적합한 타겟 목록 (배틀 루프 밖에서 유지, 소진되면 다시 조회)
1032+ var candidates []* RankingEntry
1033+
10211034 // 배틀 루프
10221035 for e .running {
10231036 if e .checkStop () {
@@ -1026,73 +1039,122 @@ func (e *Engine) loopBattle() {
10261039
10271040 e .cycleCount ++
10281041
1029- // 2. 랭킹에서 유저 목록 가져오기
1030- e . sendCommand ( "/랭킹" )
1031- time . Sleep ( 2 * time . Second )
1042+ // 타겟 목록이 비었으면 새로 조회
1043+ if len ( candidates ) == 0 {
1044+ fmt . Println ( "🔄 타겟 목록 갱신 중..." )
10321045
1033- rankingText := e . readChatText ()
1034- entries := ParseRanking ( rankingText )
1035- usernames := ExtractUsernamesFromRanking ( entries )
1046+ // 2. 랭킹에서 유저 목록 가져오기
1047+ e . sendCommand ( "/랭킹" )
1048+ time . Sleep ( 2 * time . Second )
10361049
1037- if len (usernames ) == 0 {
1038- fmt .Println ("⏳ 랭킹에서 유저를 찾을 수 없음, 30초 후 재시도..." )
1039- time .Sleep (30 * time .Second )
1040- continue
1041- }
1050+ // 랭킹은 다른 유저 이름이 포함되므로 필터링 없이 읽기
1051+ rankingText := e .readChatClipboard ()
1052+ entries := ParseRanking (rankingText )
1053+ usernames := ExtractUsernamesFromRanking (entries )
10421054
1043- // 3. 각 유저의 프로필 확인하여 적합한 타겟 찾기
1044- var target * RankingEntry
1045- minTarget := e .myProfile .Level + 1
1046- maxTarget := e .myProfile .Level + e .cfg .BattleLevelDiff
1055+ if len (usernames ) == 0 {
1056+ fmt .Println ("⏳ 랭킹에서 유저를 찾을 수 없음, 30초 후 재시도..." )
1057+ time .Sleep (30 * time .Second )
1058+ continue
1059+ }
10471060
1048- fmt .Printf ("🔍 %d명의 유저 프로필 확인 중... (타겟: +%d ~ +%d)\n " , len (usernames ), minTarget , maxTarget )
1061+ // 3. 모든 유저의 프로필 확인하여 적합한 타겟 목록 수집
1062+ minTarget := e .myProfile .Level + 1
1063+ maxTarget := e .myProfile .Level + e .cfg .BattleLevelDiff
10491064
1050- for _ , username := range usernames {
1051- if e .checkStop () {
1052- return
1053- }
1065+ fmt .Printf ("🔍 %d명의 유저 프로필 확인 중... (타겟: +%d ~ +%d)\n " , len (usernames ), minTarget , maxTarget )
10541066
1055- profile := e . CheckOtherProfile ( username )
1056- if profile == nil || profile . Level <= 0 {
1057- continue
1058- }
1067+ for _ , username := range usernames {
1068+ if e . checkStop () {
1069+ return
1070+ }
10591071
1060- if profile .Level >= minTarget && profile .Level <= maxTarget {
1061- target = & RankingEntry {
1062- Username : username ,
1063- Level : profile .Level ,
1072+ // 자기 자신은 스킵
1073+ if username == e .myProfile .Name {
1074+ continue
10641075 }
1065- fmt .Printf (" ✅ %s: +%d (적합!)\n " , username , profile .Level )
1066- break
1067- } else {
1068- fmt .Printf (" ❌ %s: +%d (범위 외)\n " , username , profile .Level )
1076+
1077+ profile := e .CheckOtherProfile (username )
1078+ if profile == nil || profile .Level <= 0 {
1079+ fmt .Printf (" ⚠️ %s: 프로필 조회 실패 또는 0레벨\n " , username )
1080+ time .Sleep (1 * time .Second )
1081+ continue
1082+ }
1083+
1084+ if profile .Level >= minTarget && profile .Level <= maxTarget {
1085+ candidates = append (candidates , & RankingEntry {
1086+ Username : username ,
1087+ Level : profile .Level ,
1088+ })
1089+ fmt .Printf (" ✅ %s: +%d (적합!)\n " , username , profile .Level )
1090+ } else {
1091+ fmt .Printf (" ❌ %s: +%d (범위 외)\n " , username , profile .Level )
1092+ }
1093+
1094+ time .Sleep (1 * time .Second ) // 프로필 조회 간격
10691095 }
10701096
1071- time .Sleep (1 * time .Second ) // 프로필 조회 간격
1097+ if len (candidates ) == 0 {
1098+ fmt .Println ("⏳ 적합한 타겟 없음, 30초 후 재시도..." )
1099+ time .Sleep (30 * time .Second )
1100+ continue
1101+ }
1102+
1103+ fmt .Printf ("📋 적합한 타겟 %d명 발견\n " , len (candidates ))
10721104 }
10731105
1074- if target == nil {
1075- fmt .Println ("⏳ 적합한 타겟 없음, 30초 후 재시도..." )
1076- time .Sleep (30 * time .Second )
1077- continue
1106+ // 적합한 타겟 중 가장 레벨이 낮은 타겟 선택 (역배 확률 최대화)
1107+ // 같은 타겟을 계속 사용 (제거하지 않음)
1108+ var target * RankingEntry
1109+ target = candidates [0 ]
1110+ for _ , c := range candidates [1 :] {
1111+ if c .Level < target .Level {
1112+ target = c
1113+ }
10781114 }
10791115
10801116 // 4. 타겟과 배틀
1081- levelDiff := target .Level - e .myProfile .Level
1117+ // 승률 계산
1118+ winRate := 0.0
1119+ if e .battleWins + e .battleLosses > 0 {
1120+ winRate = float64 (e .battleWins ) / float64 (e .battleWins + e .battleLosses ) * 100
1121+ }
1122+
10821123 fmt .Printf ("⚔️ #%d: %s (+%d) vs 나 (+%d) [%s]\n " ,
10831124 e .cycleCount , target .Username , target .Level , e .myProfile .Level , e .myProfile .SwordName )
1084- overlay .UpdateStatus ("⚔️ 자동 배틀 #%d\n 타겟: %s +%d\n 내 레벨: +%d\n \n 📋 판단: +%d차 역배 도전" , e .cycleCount , target .Username , target .Level , e .myProfile .Level , levelDiff )
1125+ overlay .UpdateStatus ("⚔️ 자동 배틀 #%d\n 타겟: %s +%d\n 내 레벨: +%d\n \n 💰 수익: %sG\n 📊 승률: %.1f%% (%d승 %d패)" ,
1126+ e .cycleCount , target .Username , target .Level , e .myProfile .Level ,
1127+ FormatGold (e .totalGold ), winRate , e .battleWins , e .battleLosses )
10851128
10861129 e .sendCommand ("/배틀 " + target .Username )
10871130 time .Sleep (3 * time .Second )
10881131
1089- // 4. 결과 확인
1132+ // 결과 확인
10901133 resultText := e .readChatText ()
10911134
10921135 // 배틀 횟수 제한 확인 (하루 10회 제한)
10931136 if DetectBattleLimit (resultText ) {
1137+ // 최종 승률 계산
1138+ finalWinRate := 0.0
1139+ if e .battleWins + e .battleLosses > 0 {
1140+ finalWinRate = float64 (e .battleWins ) / float64 (e .battleWins + e .battleLosses ) * 100
1141+ }
1142+
1143+ fmt .Println ()
1144+ fmt .Println ("════════════════════════════════════════" )
10941145 fmt .Println ("⏰ 오늘 배틀 횟수를 모두 사용했습니다 (10회/일)" )
1095- overlay .UpdateStatus ("⚔️ 자동 배틀\n ⏰ 일일 배틀 제한 도달\n 전적: %d승 %d패\n 총 수익: %sG" , e .battleWins , e .battleLosses , FormatGold (e .totalGold ))
1146+ fmt .Println ("════════════════════════════════════════" )
1147+ fmt .Printf ("📊 최종 전적: %d승 %d패 (승률 %.1f%%)\n " , e .battleWins , e .battleLosses , finalWinRate )
1148+ fmt .Printf ("💰 총 수익: %sG\n " , FormatGold (e .totalGold ))
1149+ fmt .Println ("════════════════════════════════════════" )
1150+ fmt .Println ()
1151+ fmt .Println ("엔터를 누르면 메뉴로 돌아갑니다..." )
1152+
1153+ overlay .UpdateStatus ("⚔️ 자동 배틀 완료\n ⏰ 일일 배틀 제한 도달\n \n 📊 전적: %d승 %d패\n 📈 승률: %.1f%%\n 💰 총 수익: %sG" ,
1154+ e .battleWins , e .battleLosses , finalWinRate , FormatGold (e .totalGold ))
1155+
1156+ // 사용자 입력 대기 후 메뉴 복귀
1157+ fmt .Scanln ()
10961158 return
10971159 }
10981160
@@ -1103,12 +1165,22 @@ func (e *Engine) loopBattle() {
11031165 e .battleWins ++
11041166 goldEarned = result .GoldEarned
11051167 e .totalGold += goldEarned
1168+
1169+ // 승률 업데이트
1170+ winRate = float64 (e .battleWins ) / float64 (e .battleWins + e .battleLosses ) * 100
1171+
11061172 fmt .Printf (" → 🏆 승리! +%dG (역배 성공!)\n " , goldEarned )
1107- overlay .UpdateStatus ("⚔️ 자동 배틀\n 🏆 승리! +%sG\n 전적: %d승 %d패\n \n 📋 판단: 역배 성공" , FormatGold (goldEarned ), e .battleWins , e .battleLosses )
1173+ overlay .UpdateStatus ("⚔️ 자동 배틀\n 🏆 승리! +%sG\n \n 💰 수익: %sG\n 📊 승률: %.1f%% (%d승 %d패)" ,
1174+ FormatGold (goldEarned ), FormatGold (e .totalGold ), winRate , e .battleWins , e .battleLosses )
11081175 } else {
11091176 e .battleLosses ++
1177+
1178+ // 승률 업데이트
1179+ winRate = float64 (e .battleWins ) / float64 (e .battleWins + e .battleLosses ) * 100
1180+
11101181 fmt .Println (" → 💔 패배..." )
1111- overlay .UpdateStatus ("⚔️ 자동 배틀\n 💔 패배...\n 전적: %d승 %d패\n \n 📋 판단: 역배 실패" , e .battleWins , e .battleLosses )
1182+ overlay .UpdateStatus ("⚔️ 자동 배틀\n 💔 패배...\n \n 💰 수익: %sG\n 📊 승률: %.1f%% (%d승 %d패)" ,
1183+ FormatGold (e .totalGold ), winRate , e .battleWins , e .battleLosses )
11121184 }
11131185
11141186 // 5. v2 텔레메트리 기록 (공통 헬퍼 사용)
@@ -1118,10 +1190,7 @@ func (e *Engine) loopBattle() {
11181190 // 6. 현재 통계 출력 (공통 헬퍼 사용)
11191191 PrintBattleStats (e .battleWins , e .battleLosses , e .totalGold )
11201192
1121- // 7. 프로필 갱신 (공통 헬퍼 사용)
1122- if newProfile := e .CheckProfileFull (); newProfile != nil && newProfile .Level > 0 {
1123- e .myProfile = newProfile
1124- }
1193+ // 7. 프로필 갱신은 생략 (같은 타겟 계속 사용하므로 불필요)
11251194
11261195 // 8. 쿨다운
11271196 time .Sleep (time .Duration (e .cfg .BattleCooldown * float64 (time .Second )))
@@ -1137,6 +1206,13 @@ func (e *Engine) ResetLastChatText() {
11371206 lastChatText = ""
11381207}
11391208
1209+ // SaveLastChatText 현재 채팅 텍스트를 저장 (새 응답만 감지하기 위해)
1210+ // 다른 유저 프로필 조회 등에서 명령어 전송 전에 호출
1211+ // ResetLastChatText와 달리 현재 채팅을 저장하여 새 응답만 추출 가능
1212+ func (e * Engine ) SaveLastChatText () {
1213+ lastChatText = e .readChatTextRaw ()
1214+ }
1215+
11401216// readChatText 화면에서 텍스트 읽기 (클립보드 방식)
11411217// 내 메시지만 필터링하여 반환 (다른 사람 메시지 무시)
11421218func (e * Engine ) readChatText () string {
@@ -1145,6 +1221,12 @@ func (e *Engine) readChatText() string {
11451221 return e .filterMyMessages (text )
11461222}
11471223
1224+ // readChatTextRaw 화면에서 텍스트 읽기 (필터 없음)
1225+ // 랭킹, 다른 유저 프로필 등 다른 사람 정보가 필요할 때 사용
1226+ func (e * Engine ) readChatTextRaw () string {
1227+ return e .readChatClipboard ()
1228+ }
1229+
11481230// readChatClipboard 클립보드 복사 방식으로 채팅 텍스트 읽기
11491231func (e * Engine ) readChatClipboard () string {
11501232 // 입력창 좌표 (명령어 입력용)
@@ -1265,8 +1347,19 @@ func chatLinesMatch(a, b []string) bool {
12651347
12661348// waitForResponse 플레이봇 응답 대기 (최대 maxWait 동안)
12671349// 명령어 전송 후 응답이 올 때까지 대기
1268- // 새로운 부분만 반환
1350+ // 새로운 부분만 반환 (내 메시지 필터링됨)
12691351func (e * Engine ) waitForResponse (maxWait time.Duration ) string {
1352+ return e .waitForResponseInternal (maxWait , false )
1353+ }
1354+
1355+ // waitForResponseRaw 플레이봇 응답 대기 (필터 없음)
1356+ // 랭킹, 다른 유저 프로필 등 다른 사람 정보가 필요할 때 사용
1357+ func (e * Engine ) waitForResponseRaw (maxWait time.Duration ) string {
1358+ return e .waitForResponseInternal (maxWait , true )
1359+ }
1360+
1361+ // waitForResponseInternal 응답 대기 내부 구현
1362+ func (e * Engine ) waitForResponseInternal (maxWait time.Duration , raw bool ) string {
12701363 startTime := time .Now ()
12711364 pollInterval := 500 * time .Millisecond
12721365 initialWait := 1 * time .Second
@@ -1275,7 +1368,12 @@ func (e *Engine) waitForResponse(maxWait time.Duration) string {
12751368 time .Sleep (initialWait )
12761369
12771370 for time .Since (startTime ) < maxWait {
1278- text := e .readChatText ()
1371+ var text string
1372+ if raw {
1373+ text = e .readChatTextRaw ()
1374+ } else {
1375+ text = e .readChatText ()
1376+ }
12791377 if text == "" {
12801378 time .Sleep (pollInterval )
12811379 continue
@@ -1755,6 +1853,18 @@ func (e *Engine) sendCommand(cmd string) {
17551853 input .SendCommand (e .cfg .ClickX , e .cfg .ClickY , cmd )
17561854}
17571855
1856+ // sendCommandOnce 엔터 1번만 누르는 명령어 전송
1857+ // 입력창 클리어 후 텍스트 입력, 엔터 1번 (줄바꿈만, 전송 안됨)
1858+ func (e * Engine ) sendCommandOnce (cmd string ) {
1859+ input .SendCommandOnce (e .cfg .ClickX , e .cfg .ClickY , cmd )
1860+ }
1861+
1862+ // appendAndSend 기존 입력에 텍스트 추가 후 전송
1863+ // 입력창을 클리어하지 않고 텍스트를 추가한 뒤 전송 (엔터 2번)
1864+ func (e * Engine ) appendAndSend (text string ) {
1865+ input .AppendAndSend (e .cfg .ClickX , e .cfg .ClickY , text )
1866+ }
1867+
17581868func (e * Engine ) checkStop () bool {
17591869 // 오버레이 버튼 클릭 체크
17601870 if overlay .CheckStopClicked () {
0 commit comments