Files
FFCardGame/scripts/game/UndoSystem.gd
2026-01-26 16:14:57 -05:00

200 lines
5.5 KiB
GDScript

class_name UndoSystem
extends RefCounted
## UndoSystem - Tracks the last action for undo capability
signal undo_available_changed(available: bool)
signal action_undone(action_name: String)
# Action types that can be undone
enum ActionType {
NONE,
DISCARD_FOR_CP,
DULL_BACKUP_FOR_CP,
PLAY_CARD
}
# Stored action data for undo
class UndoAction:
var type: ActionType = ActionType.NONE
var player_index: int = -1
var card: CardInstance = null
var description: String = ""
# Additional data depending on action type
var cp_element: Enums.Element = Enums.Element.FIRE
var cp_amount: int = 0
var cp_spent: Dictionary = {} # Tracks CP spent by element for proper refund
var previous_card_state: Enums.CardState = Enums.CardState.ACTIVE
var from_zone: Enums.ZoneType = Enums.ZoneType.HAND
var to_zone: Enums.ZoneType = Enums.ZoneType.FIELD_FORWARDS
var last_action: UndoAction = null
var game_state: GameState = null
func _init(state: GameState = null) -> void:
game_state = state
## Set the game state reference
func set_game_state(state: GameState) -> void:
game_state = state
## Check if undo is available
func can_undo() -> bool:
return last_action != null and last_action.type != ActionType.NONE
## Record a discard for CP action
func record_discard_for_cp(player_index: int, card: CardInstance, element: Enums.Element) -> void:
var action = UndoAction.new()
action.type = ActionType.DISCARD_FOR_CP
action.player_index = player_index
action.card = card
action.cp_element = element
action.cp_amount = 2
action.description = "Discard " + card.get_display_name() + " for CP"
last_action = action
undo_available_changed.emit(true)
## Record a dull backup for CP action
func record_dull_backup_for_cp(player_index: int, card: CardInstance, element: Enums.Element) -> void:
var action = UndoAction.new()
action.type = ActionType.DULL_BACKUP_FOR_CP
action.player_index = player_index
action.card = card
action.cp_element = element
action.cp_amount = 1
action.previous_card_state = Enums.CardState.ACTIVE # Was active before dulling
action.description = "Dull " + card.get_display_name() + " for CP"
last_action = action
undo_available_changed.emit(true)
## Record playing a card
func record_play_card(player_index: int, card: CardInstance, to_zone: Enums.ZoneType, cp_spent: Dictionary) -> void:
var action = UndoAction.new()
action.type = ActionType.PLAY_CARD
action.player_index = player_index
action.card = card
action.from_zone = Enums.ZoneType.HAND
action.to_zone = to_zone
action.cp_spent = cp_spent # Store actual CP spent for proper refund
action.description = "Play " + card.get_display_name()
last_action = action
undo_available_changed.emit(true)
## Clear the undo history (called when phase changes, combat happens, etc.)
func clear_history() -> void:
last_action = null
undo_available_changed.emit(false)
## Execute undo of the last action
func undo() -> bool:
if not can_undo() or not game_state:
return false
var action = last_action
var success = false
match action.type:
ActionType.DISCARD_FOR_CP:
success = _undo_discard_for_cp(action)
ActionType.DULL_BACKUP_FOR_CP:
success = _undo_dull_backup_for_cp(action)
ActionType.PLAY_CARD:
success = _undo_play_card(action)
if success:
var description = action.description
last_action = null
undo_available_changed.emit(false)
action_undone.emit(description)
return success
## Undo a discard for CP action
func _undo_discard_for_cp(action: UndoAction) -> bool:
var player = game_state.get_player(action.player_index)
if not player:
return false
# Remove card from break zone
if not player.break_zone.has_card(action.card):
return false
player.break_zone.remove_card(action.card)
# Add card back to hand
player.hand.add_card(action.card)
# Remove the CP that was generated
player.cp_pool.add_cp(action.cp_element, -action.cp_amount)
return true
## Undo a dull backup for CP action
func _undo_dull_backup_for_cp(action: UndoAction) -> bool:
var player = game_state.get_player(action.player_index)
if not player:
return false
# Check card is still on field
if not player.field_backups.has_card(action.card):
return false
# Reactivate the backup
action.card.activate()
# Remove the CP that was generated
player.cp_pool.add_cp(action.cp_element, -action.cp_amount)
return true
## Undo playing a card
func _undo_play_card(action: UndoAction) -> bool:
var player = game_state.get_player(action.player_index)
if not player:
return false
var card = action.card
# Remove from field
var removed = false
if action.to_zone == Enums.ZoneType.FIELD_FORWARDS:
if player.field_forwards.has_card(card):
player.field_forwards.remove_card(card)
removed = true
elif action.to_zone == Enums.ZoneType.FIELD_BACKUPS:
if player.field_backups.has_card(card):
player.field_backups.remove_card(card)
removed = true
if not removed:
return false
# Return to hand
player.hand.add_card(card)
# Reset card state
card.state = Enums.CardState.ACTIVE
card.turns_on_field = 0
# Refund the actual CP spent (by element)
if action.cp_spent.is_empty():
# Fallback: refund card cost as primary element (legacy behavior)
var cost = card.card_data.cost
var element = card.get_element()
player.cp_pool.add_cp(element, cost)
else:
# Refund each element's CP properly
for element in action.cp_spent:
player.cp_pool.add_cp(element, action.cp_spent[element])
return true
## Get description of the last action (for display)
func get_last_action_description() -> String:
if not can_undo():
return ""
return last_action.description