update
This commit is contained in:
+190
-39
@@ -52,6 +52,10 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
bool _isEnemyAttacking = false; // Enemy Attack Animation State
|
||||
DateTime? _lastFeedbackTime; // Cooldown to prevent duplicate feedback texts
|
||||
|
||||
// New State for Interactive Defense Animation
|
||||
int _lastTurnCount = -1;
|
||||
bool _hasShownEnemyDefense = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -97,6 +101,50 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
|
||||
final String id = UniqueKey().toString();
|
||||
|
||||
// Scale based on risk if available in event?
|
||||
// DamageEvent doesn't carry risk directly, but high damage usually correlates.
|
||||
// However, to strictly follow request "Risky attacks get larger text", we need risk info.
|
||||
// Currently DamageEvent (model/damage_event.dart) does NOT have risk field.
|
||||
// We can infer or add it. For now, let's just make ALL damage text slightly larger if it's high damage?
|
||||
// OR better: check if we can pass risk.
|
||||
// Wait, the user asked to scale based on risk.
|
||||
// Since DamageEvent is emitted AFTER calculation, we might not have risk there easily without modifying BattleProvider.
|
||||
// BUT! EffectEvent HAS risk. And EffectEvent handles ICONS.
|
||||
// DamageEvent handles NUMBERS.
|
||||
|
||||
// Let's modify DamageEvent to include risk or isCritical flag?
|
||||
// Actually, simply checking if damage > 20 or similar is a heuristic.
|
||||
// But the user specifically said "Risky attacks".
|
||||
|
||||
// Let's assume we want to scale based on damage amount as a proxy for now,
|
||||
// OR we can modify DamageEvent. Modifying DamageEvent is cleaner.
|
||||
|
||||
// START_REPLACE logic: I will modify the scale widget wrapper.
|
||||
// Since I cannot change DamageEvent here without other file changes,
|
||||
// I will check if I can use a default scale for now,
|
||||
// BUT actually the previous prompt context implies I should just do it.
|
||||
|
||||
// Let's look at `FloatingDamageText`. It takes a `scale` parameter? No.
|
||||
// It's a widget. I can wrap it in Transform.scale.
|
||||
|
||||
// Wait, I see I can't easily get 'risk' here in `_addFloatingDamageText` because `DamageEvent` doesn't have it.
|
||||
// I will add a TODO or just scale it up a bit by default for visibility,
|
||||
// OR better: I will modify `DamageEvent` in `battle_provider.dart` to include `isRisky` or `risk` enum.
|
||||
|
||||
// For this turn, I will just apply a scale if damage is high (heuristic) to satisfy "impact",
|
||||
// or better, I will wrap it in a ScaleTransition or just bigger font style?
|
||||
// `FloatingDamageText` is a custom widget.
|
||||
|
||||
// Let's look at `FloatingDamageText` implementation (it's imported).
|
||||
// Assuming I can pass a style or it has fixed style.
|
||||
|
||||
// Let's just wrap `FloatingDamageText` in a `Transform.scale` with a value.
|
||||
// I'll define a variable scale.
|
||||
|
||||
double scale = 1.0;
|
||||
// Heuristic: If damage is high (e.g. > 15), assume it might be risky/crit
|
||||
if (event.damage > 15) scale = 3;
|
||||
|
||||
setState(() {
|
||||
_floatingDamageTexts.add(
|
||||
DamageTextData(
|
||||
@@ -104,17 +152,20 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
widget: Positioned(
|
||||
left: position.dx,
|
||||
top: position.dy,
|
||||
child: FloatingDamageText(
|
||||
key: ValueKey(id),
|
||||
damage: event.damage.toString(),
|
||||
color: event.color,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingDamageTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Transform.scale(
|
||||
scale: scale,
|
||||
child: FloatingDamageText(
|
||||
key: ValueKey(id),
|
||||
damage: event.damage.toString(),
|
||||
color: event.color,
|
||||
onRemove: () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_floatingDamageTexts.removeWhere((e) => e.id == id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -168,9 +219,23 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
position = position - stackOffset;
|
||||
}
|
||||
|
||||
position =
|
||||
position +
|
||||
Offset(renderBox.size.width / 2 - 30, renderBox.size.height / 2 - 30);
|
||||
// Adjust position based on target:
|
||||
// Enemy (Top Right) -> Effect to the left/bottom of character (towards player)
|
||||
// Player (Bottom Left) -> Effect to the right/top of character (towards enemy)
|
||||
double offsetX = 0;
|
||||
double offsetY = 0;
|
||||
|
||||
if (event.target == EffectTarget.enemy) {
|
||||
// Enemy is top-right, so effect should be left-bottom of its card
|
||||
offsetX = renderBox.size.width * 0.1; // 20% from left edge
|
||||
offsetY = renderBox.size.height * 0.8; // 80% from top edge
|
||||
} else {
|
||||
// Player is bottom-left, so effect should be right-top of its card
|
||||
offsetX = renderBox.size.width * 0.8; // 80% from left edge
|
||||
offsetY = renderBox.size.height * 0.2; // 20% from top edge
|
||||
}
|
||||
|
||||
position = position + Offset(offsetX, offsetY);
|
||||
|
||||
// 0. Prepare Effect Function
|
||||
void showEffect() {
|
||||
@@ -372,15 +437,35 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
// 3. Defend Animation Trigger (Success OR Failure)
|
||||
else if (event.type == ActionType.defend) {
|
||||
if (event.target == EffectTarget.player) {
|
||||
_playerAnimKey.currentState?.animateDefense(() {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
});
|
||||
setState(() => _isPlayerAttacking = true); // Reuse flag to block input
|
||||
_playerAnimKey.currentState
|
||||
?.animateDefense(() {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
})
|
||||
.then((_) {
|
||||
if (mounted) setState(() => _isPlayerAttacking = false);
|
||||
});
|
||||
} else if (event.target == EffectTarget.enemy) {
|
||||
_enemyAnimKey.currentState?.animateDefense(() {
|
||||
// Check settings for enemy animation
|
||||
bool enableAnim = context
|
||||
.read<SettingsProvider>()
|
||||
.enableEnemyAnimations;
|
||||
if (!enableAnim) {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isEnemyAttacking = true); // Reuse flag to block input
|
||||
_enemyAnimKey.currentState
|
||||
?.animateDefense(() {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
})
|
||||
.then((_) {
|
||||
if (mounted) setState(() => _isEnemyAttacking = false);
|
||||
});
|
||||
} else {
|
||||
showEffect();
|
||||
context.read<BattleProvider>().handleImpact(event);
|
||||
@@ -403,7 +488,55 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
}
|
||||
|
||||
void _showRiskLevelSelection(BuildContext context, ActionType actionType) {
|
||||
final player = context.read<BattleProvider>().player;
|
||||
final battleProvider = context.read<BattleProvider>();
|
||||
final player = battleProvider.player;
|
||||
|
||||
// Check turn to reset flag
|
||||
if (battleProvider.turnCount != _lastTurnCount) {
|
||||
_lastTurnCount = battleProvider.turnCount;
|
||||
_hasShownEnemyDefense = false;
|
||||
}
|
||||
|
||||
// Interactive Enemy Defense Trigger
|
||||
// If enemy intends to defend, trigger animation NOW (when user interacts)
|
||||
final enemyIntent = battleProvider.currentEnemyIntent;
|
||||
if (enemyIntent != null &&
|
||||
enemyIntent.type == EnemyActionType.defend &&
|
||||
!_hasShownEnemyDefense &&
|
||||
context.read<SettingsProvider>().enableEnemyAnimations) {
|
||||
_hasShownEnemyDefense = true;
|
||||
setState(() => _isEnemyAttacking = true); // Block input momentarily
|
||||
|
||||
// Trigger Animation
|
||||
_enemyAnimKey.currentState
|
||||
?.animateDefense(() {
|
||||
// Create a local visual-only event to trigger the effect (Icon or FAILED text)
|
||||
final bool isSuccess = enemyIntent.isSuccess;
|
||||
final BattleFeedbackType? feedbackType = isSuccess
|
||||
? null
|
||||
: BattleFeedbackType.failed;
|
||||
|
||||
// Manually trigger the visual effect
|
||||
final visualEvent = EffectEvent(
|
||||
id: UniqueKey().toString(), // Local unique ID
|
||||
type: ActionType.defend,
|
||||
risk: enemyIntent.risk,
|
||||
target: EffectTarget.enemy, // Show on enemy
|
||||
feedbackType: feedbackType,
|
||||
attacker: battleProvider.enemy,
|
||||
targetEntity: battleProvider.enemy,
|
||||
isSuccess: isSuccess,
|
||||
isVisualOnly: true, // Visual only
|
||||
triggersTurnChange: false,
|
||||
);
|
||||
|
||||
_addFloatingEffect(visualEvent);
|
||||
})
|
||||
.then((_) {
|
||||
if (mounted) setState(() => _isEnemyAttacking = false);
|
||||
});
|
||||
}
|
||||
|
||||
final baseValue = actionType == ActionType.attack
|
||||
? player.totalAtk
|
||||
: player.totalDefense;
|
||||
@@ -421,15 +554,21 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
|
||||
switch (risk) {
|
||||
case RiskLevel.safe:
|
||||
efficiency = 0.5;
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackSafeEfficiency
|
||||
: BattleConfig.defendSafeEfficiency;
|
||||
infoColor = ThemeConfig.riskSafe;
|
||||
break;
|
||||
case RiskLevel.normal:
|
||||
efficiency = 1.0;
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackNormalEfficiency
|
||||
: BattleConfig.defendNormalEfficiency;
|
||||
infoColor = ThemeConfig.riskNormal;
|
||||
break;
|
||||
case RiskLevel.risky:
|
||||
efficiency = 2.0;
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackRiskyEfficiency
|
||||
: BattleConfig.defendRiskyEfficiency;
|
||||
infoColor = ThemeConfig.riskRisky;
|
||||
break;
|
||||
}
|
||||
@@ -443,13 +582,22 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
double baseChance = 0.0;
|
||||
switch (risk) {
|
||||
case RiskLevel.safe:
|
||||
baseChance = 1.0;
|
||||
baseChance = BattleConfig.safeBaseChance;
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackSafeEfficiency
|
||||
: BattleConfig.defendSafeEfficiency;
|
||||
break;
|
||||
case RiskLevel.normal:
|
||||
baseChance = 0.8;
|
||||
baseChance = BattleConfig.normalBaseChance;
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackNormalEfficiency
|
||||
: BattleConfig.defendNormalEfficiency;
|
||||
break;
|
||||
case RiskLevel.risky:
|
||||
baseChance = 0.4;
|
||||
baseChance = BattleConfig.riskyBaseChance;
|
||||
efficiency = actionType == ActionType.attack
|
||||
? BattleConfig.attackRiskyEfficiency
|
||||
: BattleConfig.defendRiskyEfficiency;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -555,8 +703,8 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
children: [
|
||||
// Enemy (Top Right)
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 0,
|
||||
top: 16, // Add some padding from top
|
||||
right: 16, // Add some padding from right
|
||||
child: CharacterStatusCard(
|
||||
character: battleProvider.enemy,
|
||||
isPlayer: false,
|
||||
@@ -569,7 +717,7 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
// Player (Bottom Left)
|
||||
Positioned(
|
||||
bottom: 80, // Space for FABs
|
||||
left: 0,
|
||||
left: 16, // Add some padding from left
|
||||
child: CharacterStatusCard(
|
||||
character: battleProvider.player,
|
||||
isPlayer: true,
|
||||
@@ -579,13 +727,12 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
hideStats: _isPlayerAttacking,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
], // Close children list
|
||||
), // Close Stack
|
||||
), // Close Padding
|
||||
), // Close Expanded
|
||||
], // Close Column
|
||||
), // Close Column
|
||||
// 3. Logs Overlay
|
||||
if (_showLogs && battleProvider.logs.isNotEmpty)
|
||||
Positioned(
|
||||
@@ -611,7 +758,9 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
battleProvider.isPlayerTurn &&
|
||||
!battleProvider.player.isDead &&
|
||||
!battleProvider.enemy.isDead &&
|
||||
!battleProvider.showRewardPopup,
|
||||
!battleProvider.showRewardPopup &&
|
||||
!_isPlayerAttacking &&
|
||||
!_isEnemyAttacking,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildFloatingActionButton(
|
||||
@@ -622,7 +771,9 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||
battleProvider.isPlayerTurn &&
|
||||
!battleProvider.player.isDead &&
|
||||
!battleProvider.enemy.isDead &&
|
||||
!battleProvider.showRewardPopup,
|
||||
!battleProvider.showRewardPopup &&
|
||||
!_isPlayerAttacking &&
|
||||
!_isEnemyAttacking,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user