230 lines
8.1 KiB
GDScript
230 lines
8.1 KiB
GDScript
@tool
|
|
class_name BookAPI
|
|
extends RefCounted
|
|
|
|
## Static utility class for the PageFlip2D system by Newold.
|
|
## Provides helper methods to configure the book and facilitate interaction
|
|
## from embedded scenes (UI, puzzles, maps).
|
|
##
|
|
## [b]How to use:[/b][br]
|
|
## This class is designed to be used statically. You do not need to instance it.
|
|
## Simply call [code]BookAPI.function_name()[/code] from anywhere in your project.
|
|
## It automatically tracks the active [PageFlip2D] instance.
|
|
## (When a PageFlip scene enters the tree, it registers itself as the current_book.)
|
|
|
|
# ==============================================================================
|
|
# ENUMS (Mirrored for easy access)
|
|
# ==============================================================================
|
|
|
|
enum JumpTarget {
|
|
## Jump to the closed state showing the Front Cover.
|
|
FRONT_COVER,
|
|
## Jump to the closed state showing the Back Cover.
|
|
BACK_COVER,
|
|
## Jump to a specific content page number.
|
|
CONTENT_PAGE
|
|
}
|
|
|
|
# Internal reference to the currently active book.
|
|
static var _current_book: PageFlip2D
|
|
|
|
|
|
# ==============================================================================
|
|
# SETUP & REFERENCES
|
|
# ==============================================================================
|
|
|
|
## Registers a book instance as the currently active one.
|
|
## Automatically called by [PageFlip2D] in its [method Node._ready].
|
|
static func set_current_book(book: PageFlip2D) -> void:
|
|
_current_book = book
|
|
|
|
|
|
## Returns the currently active book instance, or null if none is registered.
|
|
static func get_current_book() -> PageFlip2D:
|
|
return _current_book
|
|
|
|
|
|
# ==============================================================================
|
|
# NAVIGATION CONTROLS
|
|
# ==============================================================================
|
|
|
|
## Turns the page forward (to the next page/spread).
|
|
## Does nothing if the book is animating or at the end.
|
|
static func next_page() -> void:
|
|
if is_instance_valid(_current_book):
|
|
force_release_control(_current_book)
|
|
_current_book.next_page()
|
|
|
|
|
|
## Turns the page backward (to the previous page/spread).
|
|
## Does nothing if the book is animating or at the beginning.
|
|
static func prev_page() -> void:
|
|
if is_instance_valid(_current_book):
|
|
force_release_control(_current_book)
|
|
_current_book.prev_page()
|
|
|
|
|
|
## Forces the active book to close towards a specific cover.
|
|
## [param to_front_cover]: If true, closes to the Front Cover (Right to Left). If false, closes to Back Cover.
|
|
static func force_close_book(to_front_cover: bool) -> void:
|
|
if is_instance_valid(_current_book):
|
|
force_release_control(_current_book)
|
|
_current_book.force_close_book(to_front_cover)
|
|
|
|
|
|
## Navigates to a specific page number (1-based index).
|
|
## Acts as a wrapper for go_to_spread, calculating the correct index automatically.
|
|
## [b]ASYNC:[/b] Must be called with 'await' if animated is true.
|
|
## [param page_num]: The 1-based page number (1 = first texture in pages_paths).
|
|
## [param target]: Specifies if the target is a content page or a cover.
|
|
static func go_to_page(page_num: int = 1, target: JumpTarget = JumpTarget.CONTENT_PAGE, animated: bool = true) -> void:
|
|
var book = _current_book
|
|
if not is_instance_valid(book): return
|
|
|
|
var target_spread_idx: int = 0
|
|
|
|
match target:
|
|
JumpTarget.FRONT_COVER:
|
|
target_spread_idx = -1
|
|
JumpTarget.BACK_COVER:
|
|
target_spread_idx = book.total_spreads
|
|
JumpTarget.CONTENT_PAGE:
|
|
# Calculate spread index: 1-2 -> Spread 0, 3-4 -> Spread 1, etc.
|
|
var safe_page = max(1, page_num)
|
|
target_spread_idx = int(safe_page / 2.0)
|
|
target_spread_idx = clampi(target_spread_idx, 0, book.total_spreads - 1)
|
|
|
|
await go_to_spread(book, target_spread_idx, animated)
|
|
|
|
|
|
## Navigates to a specific spread index directly.
|
|
## [b]ASYNC:[/b] Must be called with 'await' if animated is true.
|
|
## - Animated: Fast-forwards through pages with dynamic speed.
|
|
## - Instant: Snaps to page and manually triggers the scene activation handshake.
|
|
static func go_to_spread(book: PageFlip2D, target_spread: int, animated: bool = true) -> void:
|
|
if not is_instance_valid(book): return
|
|
|
|
# Clamp target
|
|
var final_target = clampi(target_spread, -1, book.total_spreads)
|
|
var diff = final_target - book.current_spread
|
|
|
|
if diff == 0:
|
|
return
|
|
|
|
force_release_control(book)
|
|
|
|
if not animated:
|
|
# INSTANT TELEPORT
|
|
book.current_spread = final_target
|
|
book.call("_update_static_visuals_immediate")
|
|
book.call("_update_volume_visuals")
|
|
# Force check for interactive scenes after teleport
|
|
book.call("_check_scene_activation")
|
|
|
|
else:
|
|
# ANIMATED FAST-FORWARD
|
|
if book.is_animating: return # Don't interrupt an existing animation
|
|
|
|
var original_speed = book.anim_player.speed_scale
|
|
|
|
# --- DYNAMIC SPEED CALCULATION ---
|
|
var steps = abs(diff)
|
|
# Base speed: 1.5x -> Max speed: 10.0x
|
|
var dynamic_speed = remap(float(steps), 0.0, float(book.total_spreads), 1.5, 10.0)
|
|
book.anim_player.speed_scale = dynamic_speed
|
|
|
|
var going_forward = diff > 0
|
|
|
|
for i in range(steps):
|
|
if not is_instance_valid(book): break
|
|
|
|
if going_forward: book.next_page()
|
|
else: book.prev_page()
|
|
|
|
# Wait for the physical page turn to finish before starting the next one.
|
|
if book.anim_player.is_playing():
|
|
await book.anim_player.animation_finished
|
|
else:
|
|
await book.get_tree().process_frame
|
|
|
|
# RESTORE STATE
|
|
if is_instance_valid(book):
|
|
book.anim_player.speed_scale = original_speed
|
|
|
|
|
|
# ==============================================================================
|
|
# STATE & INTERACTION HELPERS
|
|
# ==============================================================================
|
|
|
|
## Returns true if the book (active or specific) is currently animating.
|
|
static func is_busy(book_instance: PageFlip2D = null) -> bool:
|
|
var book = book_instance if book_instance else _current_book
|
|
if not is_instance_valid(book): return false
|
|
return book.is_animating
|
|
|
|
|
|
## Locates the PageFlip2D controller ancestor from any node inside an interactive page.
|
|
static func find_book_controller(caller_node: Node) -> PageFlip2D:
|
|
var current = caller_node
|
|
while current:
|
|
if current is PageFlip2D:
|
|
return current
|
|
current = current.get_parent()
|
|
return null
|
|
|
|
|
|
## Safely locks or unlocks the book's ability to turn pages manually.
|
|
## WARNING: If locked, the interactive scene MUST be responsible for unlocking it later.
|
|
static func set_interaction_lock(book: PageFlip2D, locked: bool) -> void:
|
|
if not is_instance_valid(book): return
|
|
book.call("_pageflip_set_input_enabled", not locked)
|
|
|
|
|
|
## Forces the book to regain input control immediately.
|
|
## Useful as a failsafe if an interactive scene closes unexpectedly.
|
|
static func force_release_control(book: PageFlip2D) -> void:
|
|
if not is_instance_valid(book): return
|
|
book.call("_pageflip_set_input_enabled", true)
|
|
|
|
|
|
# ==============================================================================
|
|
# CONFIGURATION HELPERS
|
|
# ==============================================================================
|
|
|
|
## Configures the visual properties of a Book instance via a dictionary.
|
|
static func configure_visuals(book: PageFlip2D, data: Dictionary) -> void:
|
|
if not is_instance_valid(book): return
|
|
|
|
if "pages" in data:
|
|
book.pages_paths = data["pages"]
|
|
book.call("_prepare_book_content")
|
|
|
|
if "cover_front_out" in data: book.tex_cover_front_out = data["cover_front_out"]
|
|
if "cover_front_in" in data: book.tex_cover_front_in = data["cover_front_in"]
|
|
if "cover_back_in" in data: book.tex_cover_back_in = data["cover_back_in"]
|
|
if "cover_back_out" in data: book.tex_cover_back_out = data["cover_back_out"]
|
|
|
|
if "spine_col" in data: book.spine_color = data["spine_col"]
|
|
if "spine_width" in data:
|
|
book.spine_width = data["spine_width"]
|
|
book.call("_build_spine")
|
|
|
|
if "size" in data:
|
|
book.target_page_size = data["size"]
|
|
book.call("_apply_new_size")
|
|
|
|
book.call("_update_static_visuals_immediate")
|
|
book.call("_update_volume_visuals")
|
|
|
|
|
|
## Configures the physics simulation of the page turning effect.
|
|
static func configure_physics(book: PageFlip2D, data: Dictionary) -> void:
|
|
if not is_instance_valid(book) or not book.dynamic_poly: return
|
|
|
|
var rigger = book.dynamic_poly
|
|
for key in data.keys():
|
|
rigger.set(key, data[key])
|
|
|
|
if rigger.has_method("rebuild"):
|
|
rigger.rebuild(book.target_page_size)
|