Fix: Prevent enemy preemptive defense on first turn
This commit is contained in:
parent
fef803d064
commit
e1b000772e
|
|
@ -31,6 +31,7 @@ class EnemyIntent {
|
||||||
final String description;
|
final String description;
|
||||||
final bool isSuccess;
|
final bool isSuccess;
|
||||||
final int finalValue;
|
final int finalValue;
|
||||||
|
bool isApplied; // New field to track if effect (like defense) is already applied
|
||||||
|
|
||||||
EnemyIntent({
|
EnemyIntent({
|
||||||
required this.type,
|
required this.type,
|
||||||
|
|
@ -39,6 +40,7 @@ class EnemyIntent {
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.isSuccess,
|
required this.isSuccess,
|
||||||
required this.finalValue,
|
required this.finalValue,
|
||||||
|
this.isApplied = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +217,7 @@ class BattleProvider with ChangeNotifier {
|
||||||
isPlayerTurn = true;
|
isPlayerTurn = true;
|
||||||
showRewardPopup = false;
|
showRewardPopup = false;
|
||||||
|
|
||||||
_generateEnemyIntent(); // Generate first intent
|
_generateEnemyIntent(applyImmediate: false); // Generate first intent without applying effects
|
||||||
|
|
||||||
_addLog("Stage $stage ($type) started! A wild ${enemy.name} appeared.");
|
_addLog("Stage $stage ($type) started! A wild ${enemy.name} appeared.");
|
||||||
} else if (type == StageType.shop) {
|
} else if (type == StageType.shop) {
|
||||||
|
|
@ -419,47 +421,73 @@ class BattleProvider with ChangeNotifier {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canAct && currentEnemyIntent != null) {
|
if (canAct && currentEnemyIntent != null) {
|
||||||
final intent = currentEnemyIntent!;
|
|
||||||
|
|
||||||
if (intent.type == EnemyActionType.defend) {
|
final intent = currentEnemyIntent!;
|
||||||
// Apply defense immediately if successful, then send event
|
|
||||||
if (intent.isSuccess) {
|
|
||||||
final event = EffectEvent(
|
|
||||||
id:
|
if (intent.type == EnemyActionType.defend) {
|
||||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
|
||||||
Random().nextInt(1000).toString(),
|
// Handle Deferred Defense (from first turn)
|
||||||
type: ActionType.defend,
|
|
||||||
risk: intent.risk,
|
if (!intent.isApplied && intent.isSuccess) {
|
||||||
target: EffectTarget.enemy,
|
|
||||||
feedbackType: null,
|
// Apply defense now
|
||||||
attacker: enemy,
|
|
||||||
targetEntity: enemy,
|
final eventId = DateTime.now().millisecondsSinceEpoch.toString() + Random().nextInt(1000).toString();
|
||||||
armorGained: intent.finalValue,
|
|
||||||
isSuccess: true,
|
final event = EffectEvent(
|
||||||
);
|
|
||||||
_effectEventController.sink.add(event);
|
id: eventId,
|
||||||
handleImpact(event); // Process impact via handleImpact for safety
|
|
||||||
} else {
|
type: ActionType.defend,
|
||||||
_addLog("Enemy tried to defend but fumbled!");
|
|
||||||
final event = EffectEvent(
|
risk: intent.risk,
|
||||||
id:
|
|
||||||
DateTime.now().millisecondsSinceEpoch.toString() +
|
target: EffectTarget.enemy,
|
||||||
Random().nextInt(1000).toString(),
|
|
||||||
type: ActionType.defend,
|
feedbackType: null,
|
||||||
risk: intent.risk,
|
|
||||||
target: EffectTarget.enemy,
|
attacker: enemy,
|
||||||
feedbackType:
|
|
||||||
BattleFeedbackType.failed, // Feedback type for failed defense
|
targetEntity: enemy,
|
||||||
attacker: enemy,
|
|
||||||
targetEntity: enemy,
|
armorGained: intent.finalValue,
|
||||||
isSuccess: false,
|
|
||||||
);
|
isSuccess: true,
|
||||||
_effectEventController.sink.add(event);
|
|
||||||
handleImpact(event); // Process impact via handleImpact for safety
|
);
|
||||||
}
|
|
||||||
} else {
|
_effectEventController.sink.add(event);
|
||||||
// Attack Logic
|
|
||||||
|
_processAttackImpact(event); // Apply armor and log
|
||||||
|
|
||||||
|
intent.isApplied = true;
|
||||||
|
|
||||||
|
} else if (intent.isApplied) {
|
||||||
|
|
||||||
|
_addLog("Enemy maintains defensive stance.");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Failed defense (already logged? maybe not if deferred)
|
||||||
|
|
||||||
|
// If it was deferred fail, we should log it now?
|
||||||
|
|
||||||
|
// But _generateEnemyIntent logged fail immediately even if deferred?
|
||||||
|
|
||||||
|
// No, I changed it to log only if !success.
|
||||||
|
|
||||||
|
// Wait, previous logic: if (!success) log.
|
||||||
|
|
||||||
|
// So fail is already logged.
|
||||||
|
|
||||||
|
_addLog("Enemy tried to defend but fumbled!");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // Attack Logic
|
||||||
if (intent.isSuccess) {
|
if (intent.isSuccess) {
|
||||||
final event = EffectEvent(
|
final event = EffectEvent(
|
||||||
id:
|
id:
|
||||||
|
|
@ -711,7 +739,7 @@ class BattleProvider with ChangeNotifier {
|
||||||
_prepareNextStage();
|
_prepareNextStage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _generateEnemyIntent() {
|
void _generateEnemyIntent({bool applyImmediate = true}) {
|
||||||
if (enemy.isDead) {
|
if (enemy.isDead) {
|
||||||
currentEnemyIntent = null;
|
currentEnemyIntent = null;
|
||||||
return;
|
return;
|
||||||
|
|
@ -807,9 +835,10 @@ class BattleProvider with ChangeNotifier {
|
||||||
finalValue: armor,
|
finalValue: armor,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Apply defense immediately if successful
|
// Apply defense immediately if successful AND allowed
|
||||||
if (success) {
|
if (success && applyImmediate) {
|
||||||
enemy.armor += armor;
|
enemy.armor += armor;
|
||||||
|
currentEnemyIntent!.isApplied = true;
|
||||||
_addLog("Enemy prepares defense! (+$armor Armor)");
|
_addLog("Enemy prepares defense! (+$armor Armor)");
|
||||||
_effectEventController.sink.add(
|
_effectEventController.sink.add(
|
||||||
EffectEvent(
|
EffectEvent(
|
||||||
|
|
@ -823,7 +852,11 @@ class BattleProvider with ChangeNotifier {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_addLog("Enemy tried to defend but fumbled!");
|
// Either failed or deferred
|
||||||
|
if (!success) {
|
||||||
|
_addLog("Enemy tried to defend but fumbled!");
|
||||||
|
}
|
||||||
|
// If deferred (applyImmediate = false), we don't log or apply yet.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
# 73. Prevent Pre-emptive Defense on First Turn
|
||||||
|
|
||||||
|
## 1. 문제 (Problem)
|
||||||
|
- 현재 시스템은 `_generateEnemyIntent`가 호출되면, 적의 행동이 `Defend`일 경우 **즉시 방어도를 증가**시킵니다(Pre-emptive Defense).
|
||||||
|
- 스테이지 시작(`_prepareNextStage`) 시에도 `_generateEnemyIntent`를 호출하므로, 첫 턴부터 적이 방어도를 가지고 시작하는 경우가 발생합니다.
|
||||||
|
- 사용자는 첫 턴에는 적의 방어도가 적용되지 않은 상태로 플레이어가 선공할 수 있기를 원합니다.
|
||||||
|
|
||||||
|
## 2. 해결 방안 (Solution)
|
||||||
|
- `_generateEnemyIntent` 메서드에 `applyImmediateEffect` (기본값 `true`) 파라미터를 추가합니다.
|
||||||
|
- `_prepareNextStage`에서 첫 Intent를 생성할 때는 `applyImmediateEffect: false`로 호출하여, 의도(Intent)는 생성하되 **방어도 증가 효과는 적용하지 않도록** 합니다.
|
||||||
|
- 단, 이렇게 하면 적이 "방어하겠다"고 표시만 하고 실제로는 방어도가 0인 상태가 됩니다.
|
||||||
|
- 만약 "첫 턴에는 적이 아예 행동하지 않음"을 원한다면 Intent 자체를 생성하지 않아야 하지만, 로그라이크 특성상 "적의 다음 행동"은 보여줘야 합니다.
|
||||||
|
- 따라서 **"첫 턴의 방어 Intent는 방어도를 올리지 않는다"** 보다는 **"첫 턴 Intent 생성 시에는 방어 행동을 제외하거나, 방어도가 턴 시작 시(적 턴)에 올라가도록"** 해야 할 수도 있습니다.
|
||||||
|
- 하지만 사용자의 요청은 "첫 턴에 방어도가 적용되는 중"인 것을 막는 것이므로, **첫 Intent 생성 시에는 방어도 적용을 스킵**하는 것이 가장 직관적입니다.
|
||||||
|
|
||||||
|
## 3. 수정 계획
|
||||||
|
1. `_generateEnemyIntent({bool applyDefense = true})` 로 시그니처 변경.
|
||||||
|
2. `_prepareNextStage`에서는 `applyDefense: false`로 호출.
|
||||||
|
3. 나머지 `_enemyTurn` 등에서는 `applyDefense: true`로 호출.
|
||||||
|
|
||||||
|
**주의:** 이렇게 하면 적의 첫 행동이 `Defend`일 때, UI에는 "Defends for 10"이라고 뜨지만 실제 방어도는 0입니다. 플레이어가 공격하면 0인 상태로 맞습니다. 그리고 적 턴이 되면? 적은 `Defend` 행동을 했으므로 (이미 했다고 치고) 아무것도 안 하거나, 그때 방어도를 올릴까요?
|
||||||
|
현재 로직: `_enemyTurn`에서 `Defend`면 "Enemy maintains defensive stance" 로그만 띄우고 끝납니다.
|
||||||
|
즉, **첫 턴에 방어도를 안 올리면, 적은 첫 턴에 아무것도 안 하는 바보가 됩니다.**
|
||||||
|
|
||||||
|
**재해석:**
|
||||||
|
"첫 턴에는 무조건 플레이어가 행동하는 걸로 해야 해" -> **"적은 첫 턴에 방어 행동(선제 방어)을 하지 말아야 한다."**
|
||||||
|
즉, **첫 턴 Intent 생성 시에는 `Defend`가 나오지 않도록 강제**하거나, **첫 턴에는 Intent를 생성하되 방어 효과는 적 턴 시작 시점에 발동하도록 유예**해야 합니다.
|
||||||
|
|
||||||
|
하지만 `Slay the Spire` 같은 게임을 보면, 첫 턴에 적이 방어 자세를 취할 수도 있습니다. 사용자가 원하는 건 **"전투 시작하자마자 적이 단단해져 있는 게 싫다"**는 것입니다.
|
||||||
|
|
||||||
|
**결론:**
|
||||||
|
`_prepareNextStage`에서 Intent를 생성할 때, **방어 행동이 당첨되더라도 방어도를 즉시 적용하지 않고, 적 턴이 시작될 때 적용**하도록 로직을 분산시켜야 합니다.
|
||||||
|
하지만 현재 구조상 `Intent`는 "이미 적용됨"을 가정합니다.
|
||||||
|
|
||||||
|
**가장 깔끔한 방법:**
|
||||||
|
**첫 턴(`_prepareNextStage`)에서는 `_generateEnemyIntent`를 호출하지 않습니다.**
|
||||||
|
대신 `null`로 두거나, **"대기(Wait/Stunned)"** 상태로 둡니다.
|
||||||
|
하지만 적이 뭘 할지 모르면 전략을 짤 수 없습니다.
|
||||||
|
|
||||||
|
**사용자의 의도:** "첫 턴에는 무조건 플레이어가 행동" -> 플레이어 선공 보장.
|
||||||
|
현재도 플레이어 선공입니다. 단지 적이 "방어 버프"를 두르고 있을 뿐.
|
||||||
|
|
||||||
|
**수정 제안:**
|
||||||
|
`_prepareNextStage`에서 생성하는 첫 Intent는 **`applyDefense: false`**로 생성합니다.
|
||||||
|
그리고 `_enemyTurn`에서 **"만약 현재 Intent가 Defend이고 방어도가 아직 적용 안 됐다면(혹은 첫 턴이라면), 이때 방어도를 적용한다"**는 로직을 추가해야 합니다.
|
||||||
|
|
||||||
|
하지만 코드가 복잡해집니다.
|
||||||
|
더 쉬운 방법: **첫 턴 Intent 생성 시, 아예 `Defend` 선택지를 배제합니다.** (첫 턴은 무조건 공격만 하도록).
|
||||||
|
이게 밸런스상으론 더 공격적일 수 있지만, "방어도가 적용되는 문제"는 확실히 해결됩니다.
|
||||||
|
|
||||||
|
**사용자 멘트 재확인:** "스테이지 첫번째 enemyIntent 생성 시... isFirst 플래그를 추가해서 첫턴에는 무조건 플레이어가 행동하는걸로 해야해."
|
||||||
|
-> 이 말은 **"적이 아무런 선제 액션(방어)도 취하지 않은 상태여야 한다"**는 뜻입니다.
|
||||||
|
|
||||||
|
**전략:**
|
||||||
|
`BattleProvider`에 `isFirstTurn` 플래그를 추가하는 것이 아니라, `_generateEnemyIntent` 메서드에 `isFirstTurn` 파라미터를 추가합니다.
|
||||||
|
그리고 `isFirstTurn`이 `true`이면, **`Defend` 확률을 0으로 만들거나, `Defend`가 선택되어도 방어도를 적용하지 않고 넘깁니다.**
|
||||||
|
후자가 낫습니다. UI에는 "방어 예정"이라고 뜨고, 적 턴이 되었을 때 방어도가 올라가는 것이 가장 자연스럽습니다.
|
||||||
|
|
||||||
|
**구현:**
|
||||||
|
1. `_generateEnemyIntent({bool applyImmediate = true})`
|
||||||
|
2. `_prepareNextStage` -> `_generateEnemyIntent(applyImmediate: false)`
|
||||||
|
3. `_enemyTurn` -> 만약 `currentEnemyIntent`가 `Defend`인데 방어도가 0이라면? (이미 깎였을 수도 있음).
|
||||||
|
-> **Flag 필요:** `bool _isPreemptiveDefenseApplied` 같은 변수가 필요할 수도 있습니다.
|
||||||
|
|
||||||
|
아니면 단순히 **`_prepareNextStage`에서는 `Defend`가 안 나오게 막는 것**이 제일 깔끔합니다. (첫 턴은 공격만 함).
|
||||||
|
하지만 사용자가 "첫턴에는 무조건 플레이어가 행동"이라고 했지 "적은 공격만 해"라고 하진 않았습니다.
|
||||||
|
|
||||||
|
**가장 적절한 구현:**
|
||||||
|
`_prepareNextStage`에서 Intent 생성 시 `applyImmediate: false`로 설정.
|
||||||
|
그리고 `_enemyTurn` 로직을 수정하여, **"Defend Intent인 경우, 기존에는 '이미 적용됨'이라 넘어갔지만, 이제는 '적용 안 됐으면 지금 적용'하도록"** 변경.
|
||||||
|
|
||||||
|
어떻게 '적용 안 됐음'을 알까요?
|
||||||
|
`EnemyIntent` 클래스에 `bool applied` 필드를 추가하면 됩니다!
|
||||||
|
|
||||||
|
**계획:**
|
||||||
|
1. `EnemyIntent` 클래스에 `bool isApplied` 필드 추가 (기본 false).
|
||||||
|
2. `_generateEnemyIntent`에서 `applyImmediate`가 true면 방어도 올리고 `isApplied = true`.
|
||||||
|
3. `_prepareNextStage`에서는 `applyImmediate: false`로 호출 -> `isApplied = false`.
|
||||||
|
4. `_enemyTurn`에서 `Intent`가 `Defend`일 때, `!isApplied`라면 방어도 적용하고 이펙트 띄움.
|
||||||
|
|
||||||
|
이 방식이 완벽합니다.
|
||||||
Loading…
Reference in New Issue