# 낚시 미니게임 구현 기획안

이 문서는 Kord의 `낚시(Fishing)` 미니게임 시스템에 대한 설계 방향과 구현 범위를 정리한 기획서입니다.

## 개요

낚시 미니게임은 기존 미니게임 경제 시스템에서 사용할 재화를 공급하는, 실시간 이모지 조작형 미니게임입니다.

사용자는 1초마다 갱신되는 상태 메시지를 보며 입력을 미리 선택하고, 물고기가 특정 위치에 일정 시간 머문 뒤 그 입력이 맞았는지 판정받습니다. 핵심 목표는 물고기를 끌어와 거리를 줄이는 동시에, 낚시줄 끊어짐 게이지가 가득 차지 않도록 관리하는 것입니다.

이 게임은 단순 슬래시 명령 기반이 아니라, 버튼 조작과 실시간 게이지 변화, 물고기 이모지 표시를 통해 더 직접적인 조작감을 주는 것을 목표로 합니다.

## 핵심 게임 루프

### 1. 스레드 입장과 세션 시작

- 사용자는 먼저 `/fishing enter` 명령으로 낚시 전용 스레드에 입장합니다.
- 봇은 명령을 실행한 채널에 `UserName's Fishing Spot` 형식의 전용 스레드를 생성합니다.
- 이미 같은 사용자의 낚시 스레드가 있으면 기존 스레드를 재사용합니다.
- 실제 낚시 세션 시작은 전용 스레드 안에서 `/fishing cast`를 입력했을 때만 가능합니다.
- 즉, 스레드 생성과 게임 시작은 별도 단계로 분리합니다.
- 봇은 실시간 게임 메시지를 생성합니다.
- 메시지에는 다음 요소가 포함됩니다.
- 중앙에 표시되는 물고기 이모지
- 거리 게이지
- 낚시줄 끊어짐 게이지
- 네 개의 조작 버튼
- 현재 라운드에 예약된 입력
- 다음 판정까지 남은 시간

### 1-1. 전용 스레드 규칙

- 스레드 이름은 영문 기준 `UserName's Fishing Spot` 형식을 사용합니다.
- 낚시 게임 메시지, 버튼 입력, 결과 메시지는 모두 해당 스레드 안에서만 처리합니다.
- 이렇게 하면 일반 채팅이 밀려도 낚시 UI가 섞이지 않고 유지됩니다.
- 한 사용자는 동시에 하나의 낚시 스레드만 활성화할 수 있습니다.
- `/fishing cast`는 자신의 낚시 스레드 안에서만 사용할 수 있습니다.
- 성공이나 실패로 게임이 끝나더라도 스레드는 즉시 삭제하지 않습니다.
- 같은 스레드 안에서 다시 `/fishing cast`를 입력해 연속 플레이할 수 있어야 합니다.
- `/fishing end`를 실행했을 때만 세션을 정리하고 스레드를 삭제합니다.

### 2. 조작 버튼

사용자는 아래 네 가지 버튼 중 하나를 선택할 수 있습니다.

- `⬅️` 왼쪽
- `⏺️` 중앙
- `➡️` 오른쪽
- `🛌` 휴식

### 3. 물고기 위치

- 물고기는 텍스트가 아니라 이모지로 표시됩니다.
- 물고기 이모지는 메시지의 시각적 중앙 라인에 위치해야 합니다.
- 내부 상태상 물고기 위치는 왼쪽, 중앙, 오른쪽 중 하나를 가집니다.
- 물고기는 한 라운드 동안 같은 위치에 일정 시간 머뭅니다.
- 사용자는 그 라운드 안에서 방향 버튼을 미리 입력합니다.
- 물고기가 해당 위치에 일정 시간 이상 머문 뒤 입력이 일치하면 성공 판정이 납니다.
- 입력이 없거나 다른 방향이면 `타이밍 빗나감`으로 처리됩니다.

### 4. 거리 게이지

- 물고기를 얼마나 더 끌어와야 하는지를 나타내는 게이지입니다.
- 시작값은 `100` 같은 양수로 설정합니다.
- 방향을 맞춘 입력이 성공 판정되면 거리가 감소합니다.
- 휴식을 선택하면 거리가 다시 증가합니다.
- 거리가 `0`이 되면 낚시에 성공합니다.

### 5. 낚시줄 끊어짐 게이지

- 낚시줄이 얼마나 끊어질 위기에 가까운지를 나타내는 게이지입니다.
- 방향을 맞춘 입력이 성공 판정되면 이 게이지가 증가합니다.
- 타이밍을 놓치면 소량 증가합니다.
- 휴식을 선택하면 이 게이지가 회복됩니다.
- 게이지가 최대치에 도달하면 즉시 낚시에 실패합니다.

### 6. 휴식 행동

- `🛌` 휴식은 해당 라운드에서 즉시 적용됩니다.
- 낚시줄 끊어짐 게이지를 회복합니다.
- 대신 물고기와의 거리가 다시 증가합니다.
- 즉, 휴식은 실패 위험을 낮추지만 진행도를 되돌리는 안전 선택지입니다.

### 7. 틱 업데이트

- 낚시 상태는 1초마다 갱신됩니다.
- 메시지도 1초 주기로 갱신됩니다.
- 이 갱신 루프는 아래 요소를 처리합니다.
- 현재 라운드 경과 시간 계산
- 입력 예약 상태 반영
- 성공 / 타이밍 빗나감 판정
- 다음 물고기 위치로 라운드 전환
- 게이지 렌더링 갱신
- 세션 종료 판정

## UI / UX 요구사항

### 실시간 메시지 구성

낚시 메시지는 최소한 아래 요소를 포함해야 합니다.

- 물고기 이모지로 표시되는 현재 위치
- 거리 게이지
- 낚시줄 끊어짐 게이지
- 간단한 상태 텍스트
- 현재 예약 입력
- 다음 판정까지 남은 시간
- 예: `입질 중`, `타이밍 빗나감`, `휴식 중`, `성공`, `실패`

### 게이지 디자인

항상 아래 두 개의 게이지가 동시에 보여야 합니다.

1. 거리 게이지
- 성공을 향해 감소하는 게이지
- 휴식 시 다시 증가할 수 있음
- 채워진 블록 / 빈 블록 형태로 표현 가능

2. 낚시줄 끊어짐 게이지
- 실패를 향해 증가하는 게이지
- 거리 게이지와 시각적으로 구분되어야 함

예시:

```text
위치:           ⬅️   🐟   ➡️
입력:           ⏺️
남은 시간:      2s
거리:           ████████░░  20 / 100
끊어짐:         ████░░░░░░  40 / 100
```

최종 렌더링은 더 다듬을 수 있지만, 핵심은 물고기 자체를 반드시 이모지로 유지하는 것입니다.

### 조작 버튼

버튼은 직관적이고 빠르게 눌릴 수 있어야 하므로, 이모지 중심으로 구성합니다.

- `⬅️`
- `⏺️`
- `➡️`
- `🛌`

## 성공 / 실패 조건

### 성공

- 거리 게이지가 `0`이 됩니다.
- 보상을 지급합니다.
- 세션을 종료하고 버튼을 비활성화합니다.

### 실패

- 낚시줄 끊어짐 게이지가 최대치에 도달합니다.
- 세션을 종료하고 버튼을 비활성화합니다.

### 선택적 시간 제한

- 일정 시간 동안 행동하지 않으면 실패하도록 설계할 수도 있습니다.
- 다만 이 기능은 MVP에는 필수가 아니며, 템포가 느릴 때만 후속 단계에서 검토합니다.

### 종료 명령

- `/fishing end` 명령을 제공해 사용자가 직접 낚시 스레드를 종료할 수 있어야 합니다.
- 종료 명령이 실행되면 진행 중인 낚시 세션이 있다면 즉시 정리됩니다.
- 전용 스레드는 `/fishing end`에서만 삭제됩니다.
- 성공/실패 후에는 버튼만 비활성화하고 스레드는 유지합니다.
- 사용자는 같은 스레드 안에서 다시 `/fishing cast`를 입력해 새 게임을 시작할 수 있어야 합니다.

## 경제 시스템 연동

낚시는 기존 미니게임 경제를 보조하는 수단으로 설계합니다.

### 권장 보상 모델

- 낚시에 성공하면 `gold`를 지급합니다.
- 지급된 `gold`는 현재 `refinement` 게임이 사용하는 경제 프로필에 반영합니다.
- 이렇게 하면 낚시는 정련 게임을 보조하는 재화 수급형 미니게임이 됩니다.

### 확장 가능성

후속 버전에서는 아래 요소를 추가할 수 있습니다.

- 물고기 희귀도
- 물고기 크기(cm) 시스템
- 개별 물고기 인벤토리
- 물고기 도감 / 컬렉션
- 미끼 종류
- 낚싯대 및 업그레이드
- 물고기 판매 시스템

Phase 1에서는 직접 `gold`를 주는 방식이 가장 단순하고 호환성이 좋습니다.

## 설정 모델

낚시는 공용 미니게임 레지스트리에 등록되는 구조로 가야 합니다.

### MiniGame Registry

추가할 키:

- `fishing`

이를 통해 다음이 가능해집니다.

- 길드별 활성화 / 비활성화
- 전용 채널 제한
- `/minigame`을 통한 상태 조회

낚시의 경우 전용 채널 안에서 다시 개별 스레드를 생성하는 방식으로 운영합니다.

## 기술 구조

### 서비스

권장 신규 서비스:

- `FishingService`

주요 책임:

- 낚시 전용 스레드 생성 / 삭제
- 세션 생성 / 종료
- 사용자별 활성 낚시 세션 추적
- 틱 업데이트 루프 관리
- 물고기 위치 생성
- 입력 예약 및 라운드 판정
- 보상 정산

### 인터랙션 처리

권장 커스텀 ID prefix:

- `fishing_`

기존 구조와 일관성을 유지합니다.

- `music_`
- `refine_`

### 영속 데이터

권장 초기 모델:

- `FishingProfile`
- `userId`
- `guildId`
- `totalCatchCount`
- `successCount`
- `failCount`
- `bestCatchReward`
- `lastCastAt`
- `createdAt`
- `updatedAt`

이 정도면 초반 통계 추적에 충분하고, 너무 이르게 인벤토리를 과설계하지 않을 수 있습니다.

도감/크기 확장 시에는 아래 모델을 추가합니다.

- `FishingCollectionEntry`
- `userId`
- `guildId`
- `fishId`
- `catchCount`
- `bestRarity`
- `bestSizeCm`
- `lastCaughtAt`
- `createdAt`
- `updatedAt`

이 모델은 유저가 어떤 물고기를 몇 번 잡았는지, 최고 레어도와 최고 크기가 무엇인지 추적하는 데 사용합니다.

### 물고기 크기 시스템

- 낚시 성공 시 물고기마다 `cm` 단위의 크기를 부여합니다.
- 기본 크기 범위는 물고기별 데이터에서 관리합니다.
- 최종 크기는 `물고기 기본 크기 범위 × 레어도 보정치`로 계산합니다.
- 즉, 같은 물고기라도 레어도가 높을수록 더 큰 개체가 등장할 수 있습니다.
- 성공 결과 메시지에는 잡은 물고기의 크기를 함께 표시합니다.
- 도감에는 해당 물고기의 최고 크기를 기록합니다.

### 도감 (Dex / Collection)

- `/fishing dex` 명령을 통해 개인 도감을 조회할 수 있어야 합니다.
- 도감에는 아래 정보를 보여줍니다.
- 잡아본 물고기 목록
- 각 물고기의 포획 횟수
- 최고 레어도
- 최고 크기(cm)
- 마지막 포획 시각
- 아직 잡지 못한 물고기는 후속 버전에서 실루엣/잠금 상태로 표시할 수 있습니다.

### 세션 모델

낚시 플레이 자체는 즉각적인 반응을 위해 메모리 세션 기반이 적합합니다.

권장 런타임 필드:

- `guildId`
- `userId`
- `threadId`
- `messageId`
- `fishPosition`
- `selectedAction`
- `phaseStartedAt`
- `distance`
- `lineTension`
- `tickInterval`
- `expiresAt`

## 입력 판정 규칙

권장 MVP 규칙은 아래와 같습니다.

- 각 라운드마다 물고기는 하나의 위치에 일정 시간 머뭅니다.
- 플레이어는 그 라운드에서 방향 입력을 미리 예약합니다.
- 물고기가 해당 위치에 `특정 시간 이상` 머문 시점에 입력이 일치하면
- 거리 감소
- 낚시줄 끊어짐 게이지 증가
- 성공 판정 후 다음 라운드로 이동
- 라운드 시간이 끝날 때까지 올바른 입력이 없으면
- `타이밍 빗나감`
- 거리 감소 없음
- 소량의 끊어짐 증가
- 휴식을 선택하면
- 낚시줄 끊어짐 게이지 감소
- 대신 거리 증가
- 휴식 적용 후 즉시 다음 라운드로 이동

이 구조는 다음 긴장감을 만듭니다.

- 방향을 잘 예약해 두면 안정적으로 거리 감소를 만들 수 있음
- 너무 공격적으로 당기면 줄이 끊어질 위험이 커짐
- 휴식은 실패를 막아주지만 물고기가 다시 멀어짐

## 단계별 계획

### Phase 1

- `fishing`을 미니게임 레지스트리에 등록
- `/fishing enter` 추가
- `/fishing cast` 추가
- `/fishing end` 추가
- `/fishing enter` 실행 시 `UserName's Fishing Spot` 스레드 생성
- `/fishing cast`는 전용 스레드 안에서만 허용
- 메모리 기반 낚시 세션 루프 구현
- 1초 주기 메시지 갱신
- 입력 예약형 라운드 판정 구현
- 두 개의 게이지 표시
- 네 개의 이모지 조작 버튼 추가
- 성공 시 `gold` 지급
- 성공/실패 후 스레드 유지
- `/fishing end` 실행 시 스레드 삭제

### Phase 2

- `/fishing status` 추가
- `/fishing ranking` 추가
- 사용자별 낚시 통계 추가
- 낚시 프로필 영속화
- 물고기 이동 패턴 개선
- 보상 밸런스 조정
- `/fishing dex` 추가
- 물고기 크기(cm) 시스템 추가
- 도감용 포획 기록 저장
- 길드 내 최고 크기 기준 Top 10 랭킹 표시

### Phase 3

- 희귀도 체계 추가
- 인벤토리 / 도감 고도화
- 미끼 / 낚싯대 보정치 추가
- 리더보드 지원
- 미포획 물고기 잠금/실루엣 UI 추가

## 검증 / 테스트

### 수동 테스트 시나리오

1. 허용된 채널에서 `/fishing enter` 실행
2. `UserName's Fishing Spot` 스레드가 자동 생성되는지 확인
3. 자신의 낚시 스레드 밖에서는 `/fishing cast`가 거부되는지 확인
4. 낚시 관련 메시지가 스레드 안에서만 진행되는지 확인
5. 실시간 메시지에 물고기 이모지와 두 개의 게이지가 보이는지 확인
6. 방향 버튼을 미리 눌러 예약 입력이 반영되는지 확인
7. 일정 시간 뒤 방향이 맞았을 때 거리 감소가 적용되는지 확인
8. 타이밍을 놓치면 `타이밍 빗나감`으로 처리되는지 확인
9. 휴식을 눌렀을 때 끊어짐 게이지가 회복되고 거리가 증가하는지 확인
10. 거리 `0` 도달 시 성공 처리되는지 확인
11. 끊어짐 게이지 최대 도달 시 실패 처리되는지 확인
12. 세션 종료 후 버튼이 비활성화되고 스레드는 유지되는지 확인
13. 같은 스레드 안에서 다시 `/fishing cast`가 가능한지 확인
14. `/fishing end` 실행 시 스레드가 자동 삭제되는지 확인
15. 보상이 정상 지급되는지 확인
16. `/minigame` 설정 기반 채널 제한이 적용되는지 확인

### 엣지 케이스

- 같은 사용자가 중복으로 세션 시작
- 스레드 삭제 권한이 없을 때 종료 처리
- 세션 종료 후 오래된 버튼 클릭
- 낚시 세션 중 봇 재시작
- 인터랙션 응답 지연
- 1초 주기 메시지 갱신 시 Discord rate limit 영향

## 참고 사항

- 1초 갱신 루프는 이 게임의 손맛을 좌우하므로 메시지 갱신 성능을 꼭 확인해야 합니다.
- Discord 메시지는 게임 엔진이 아니므로, 순간 반응보다는 명확한 판정 윈도우가 더 중요합니다.
- 최종 설계는 화려함보다 반응성, 명확성, 실패 상태의 가독성을 우선해야 합니다.
