Skip to content

Commit 1063837

Browse files
StopDragonclaude
andcommitted
feat: 배틀 모드 개선 및 프로필 조회 버그 수정 (v2.5.3)
## 배틀 모드 개선 - 같은 타겟에게 계속 배틀 (효율적인 역배) - 오버레이에 수익/승률 실시간 표시 - 배틀 횟수 제한(10회) 도달 시 통계 출력 후 메뉴 복귀 - 프로필 조회 후 타겟 목록 캐싱 (불필요한 조회 제거) ## 프로필 조회 버그 수정 - ParseProfileForUser: 특정 유저의 프로필 섹션만 추출하여 파싱 - 다른 유저 조회 시 본인 프로필(+6)로 인식되던 버그 수정 - 배틀 횟수 제한 패턴 개선 (실제 메시지에 맞게) ## 입력 처리 수정 - AppendAndSend: 엔터 2번으로 변경 (카카오톡 전송 방식) - SaveLastChatText: 새 응답만 감지하기 위한 함수 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a95ae48 commit 1063837

File tree

5 files changed

+313
-87
lines changed

5 files changed

+313
-87
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.5.1"
22+
const VERSION = "2.5.3"
2323

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

internal/game/engine.go

Lines changed: 186 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -554,28 +554,26 @@ func (e *Engine) printSessionStats() {
554554
}
555555

556556
func (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

10001008
func (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
// 내 메시지만 필터링하여 반환 (다른 사람 메시지 무시)
11421218
func (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 클립보드 복사 방식으로 채팅 텍스트 읽기
11491231
func (e *Engine) readChatClipboard() string {
11501232
// 입력창 좌표 (명령어 입력용)
@@ -1265,8 +1347,19 @@ func chatLinesMatch(a, b []string) bool {
12651347

12661348
// waitForResponse 플레이봇 응답 대기 (최대 maxWait 동안)
12671349
// 명령어 전송 후 응답이 올 때까지 대기
1268-
// 새로운 부분만 반환
1350+
// 새로운 부분만 반환 (내 메시지 필터링됨)
12691351
func (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+
17581868
func (e *Engine) checkStop() bool {
17591869
// 오버레이 버튼 클릭 체크
17601870
if overlay.CheckStopClicked() {

0 commit comments

Comments
 (0)