mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-18 03:26:31 +01:00
[MIRROR] Contextual tutorials for swapping hands and dropping items [MDB IGNORE] (#18604)
* Contextual tutorials for swapping hands and dropping items * Fixes * Oops Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> Co-authored-by: Useroth <37159550+Useroth@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
/// The base for a contextual tutorial.
|
||||
/// In order to give a tutorial to someone, use `SStutorials.suggest_tutorial(user, /datum/tutorial/subtype)`
|
||||
/datum/tutorial
|
||||
/// If set, any account who started playing before this date will not be given this tutorial.
|
||||
/// Date is in YYYY-MM-DD format.
|
||||
var/grandfather_date
|
||||
|
||||
/// The mob we are giving the tutorial to
|
||||
VAR_PROTECTED/mob/user
|
||||
|
||||
VAR_PRIVATE/atom/movable/screen/tutorial_instruction/instruction_screen
|
||||
|
||||
/datum/tutorial/New(mob/user)
|
||||
src.user = user
|
||||
|
||||
RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(destroy_self))
|
||||
RegisterSignal(user.client, COMSIG_PARENT_QDELETING, PROC_REF(destroy_self))
|
||||
|
||||
/datum/tutorial/Destroy(force, ...)
|
||||
user.client?.screen -= instruction_screen
|
||||
QDEL_NULL(instruction_screen)
|
||||
|
||||
user = null
|
||||
|
||||
return ..()
|
||||
|
||||
/// Gets the [`/datum/tutorial_manager`] that owns this tutorial.
|
||||
/datum/tutorial/proc/manager()
|
||||
RETURN_TYPE(/datum/tutorial_manager)
|
||||
return SStutorials.tutorial_managers[type]
|
||||
|
||||
/// The actual steps of the tutorial. Is given any excess arguments of suggest_tutorial.
|
||||
/// Must be overridden.
|
||||
/datum/tutorial/proc/perform()
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
CRASH("[type] does not override perform()")
|
||||
|
||||
/// Returns TRUE/FALSE if this tutorial should be given.
|
||||
/// If FALSE, does not mean it won't come back later.
|
||||
/datum/tutorial/proc/should_perform()
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
return TRUE
|
||||
|
||||
/// Called by the tutorial when the user has successfully completed it.
|
||||
/// Will mark it as completed in the datbaase and kick off destruction of the tutorial.
|
||||
/datum/tutorial/proc/complete()
|
||||
SIGNAL_HANDLER
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
manager().complete(user)
|
||||
perform_base_completion_effects()
|
||||
|
||||
/// As opposed to `complete()`, this merely hides the tutorial.
|
||||
/// This should be used when the user doesn't need the tutorial anymore, but didn't
|
||||
/// actually properly finish it.
|
||||
/datum/tutorial/proc/dismiss()
|
||||
SIGNAL_HANDLER
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
manager().dismiss(user)
|
||||
perform_base_completion_effects()
|
||||
|
||||
#define INSTRUCTION_SCREEN_DELAY (1 SECONDS)
|
||||
|
||||
/datum/tutorial/proc/perform_base_completion_effects()
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
var/delay = perform_completion_effects_with_delay()
|
||||
|
||||
if (!isnull(instruction_screen))
|
||||
animate(instruction_screen, time = INSTRUCTION_SCREEN_DELAY, alpha = 0, easing = SINE_EASING)
|
||||
delay += INSTRUCTION_SCREEN_DELAY
|
||||
|
||||
QDEL_IN(src, delay)
|
||||
|
||||
/// Called when the tutorial is being hidden, but before it is deleted.
|
||||
/// You should unregister signals and fade out any of your creations in here.
|
||||
/// Returns how long extra to delay the deletion.
|
||||
/datum/tutorial/proc/perform_completion_effects_with_delay()
|
||||
SHOULD_CALL_PARENT(FALSE)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
return 0
|
||||
|
||||
#undef INSTRUCTION_SCREEN_DELAY
|
||||
|
||||
/datum/tutorial/proc/destroy_self()
|
||||
SIGNAL_HANDLER
|
||||
PRIVATE_PROC(TRUE)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
manager().dismiss(user)
|
||||
qdel(src)
|
||||
|
||||
/// Shows a large piece of text on the user's screen with the given message.
|
||||
/// If a message already exists, will fade it out and replace it.
|
||||
/datum/tutorial/proc/show_instruction(message)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
if (isnull(instruction_screen))
|
||||
instruction_screen = new(null, message, user.client)
|
||||
user.client?.screen += instruction_screen
|
||||
else
|
||||
instruction_screen.change_message(message)
|
||||
|
||||
/// Given a keybind and a message, will replace %KEY% in `message` with the first keybind they have.
|
||||
/// As a fallback, will return the third parameter, `message_without_keybinds`, if none are set.
|
||||
/datum/tutorial/proc/keybinding_message(datum/keybinding/keybinding_type, message, message_without_keybinds)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
var/list/keybinds = user.client?.prefs.key_bindings[initial(keybinding_type.name)]
|
||||
return keybinds?.len > 0 ? replacetext(message, "%KEY%", "<b>[keybinds[1]]</b>") : message_without_keybinds
|
||||
|
||||
/// Creates a UI element with the given `icon_state`, starts it at `initial_screen_loc`, and animates it to `target_screen_loc`.
|
||||
/// Waits `animate_start_time` before moving.
|
||||
/datum/tutorial/proc/animate_ui_element(icon_state, initial_screen_loc, target_screen_loc, animate_start_time)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
var/atom/movable/screen/preview = new
|
||||
preview.icon = ui_style2icon(user.client?.prefs.read_preference(/datum/preference/choiced/ui_style) || GLOB.available_ui_styles[1])
|
||||
preview.icon_state = icon_state
|
||||
preview.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
preview.screen_loc = "1,1"
|
||||
|
||||
var/view = user.client?.view
|
||||
|
||||
var/list/origin_offsets = screen_loc_to_offset(initial_screen_loc, view)
|
||||
|
||||
// A little offset to the right
|
||||
var/matrix/origin_transform = TRANSLATE_MATRIX(origin_offsets[1] - world.icon_size * 0.5, origin_offsets[2] - world.icon_size * 1.5)
|
||||
|
||||
var/list/target_offsets = screen_loc_to_offset(target_screen_loc, view)
|
||||
// `- world.icon_Size * 0.5` to patch over a likely bug in screen_loc_to_offset with CENTER, needs more looking at
|
||||
var/matrix/animate_to_transform = TRANSLATE_MATRIX(target_offsets[1] - world.icon_size * 1.5, target_offsets[2] - world.icon_size)
|
||||
|
||||
preview.transform = origin_transform
|
||||
|
||||
preview.alpha = 0
|
||||
animate(preview, time = animate_start_time, alpha = 255, easing = CUBIC_EASING)
|
||||
animate(1.4 SECONDS)
|
||||
animate(transform = animate_to_transform, time = 2 SECONDS, easing = SINE_EASING | EASE_IN)
|
||||
animate(alpha = 0, time = 2.4 SECONDS, easing = CUBIC_EASING | EASE_IN, flags = ANIMATION_PARALLEL)
|
||||
|
||||
user.client?.screen += preview
|
||||
|
||||
return preview
|
||||
|
||||
/// A singleton that manages when to create tutorials of a specific tutorial type.
|
||||
/datum/tutorial_manager
|
||||
VAR_PRIVATE/datum/tutorial/tutorial_type
|
||||
|
||||
/// ckeys that we know have finished the tutorial
|
||||
VAR_PRIVATE/list/finished_ckeys = list()
|
||||
|
||||
/// ckeys that have performed the tutorial, but have not completed it.
|
||||
/// Doesn't mean that they can still see the tutorial, might have meant the tutorial was dismissed
|
||||
/// without being completed, such as during a log out.
|
||||
VAR_PRIVATE/list/performing_ckeys = list()
|
||||
|
||||
/datum/tutorial_manager/New(tutorial_type)
|
||||
ASSERT(ispath(tutorial_type, /datum/tutorial))
|
||||
src.tutorial_type = tutorial_type
|
||||
|
||||
/datum/tutorial_manager/Destroy(force, ...)
|
||||
if (!force)
|
||||
stack_trace("Something is trying to destroy [type], which is a singleton")
|
||||
return QDEL_HINT_LETMELIVE
|
||||
return ..()
|
||||
|
||||
/// Checks if we should perform the tutorial for the given user, and performs if so.
|
||||
/// Use `SStutorials.suggest_tutorial` instead of calling this directly.
|
||||
/datum/tutorial_manager/proc/try_perform(mob/user, list/arguments)
|
||||
var/datum/tutorial/tutorial = new tutorial_type(user)
|
||||
if (!tutorial.should_perform(user))
|
||||
qdel(tutorial)
|
||||
return
|
||||
|
||||
performing_ckeys[user.ckey] = TRUE
|
||||
|
||||
tutorial.perform(arglist(arguments))
|
||||
|
||||
/// Checks if the user should be given this tutorial
|
||||
/datum/tutorial_manager/proc/should_run(mob/user)
|
||||
var/ckey = user.ckey
|
||||
|
||||
if (isnull(ckey))
|
||||
return FALSE
|
||||
|
||||
if (ckey in finished_ckeys)
|
||||
return FALSE
|
||||
|
||||
if (ckey in performing_ckeys)
|
||||
return FALSE
|
||||
|
||||
if (!SSdbcore.IsConnected())
|
||||
return CONFIG_GET(flag/give_tutorials_without_db)
|
||||
|
||||
var/player_join_date = user.client?.player_join_date
|
||||
if (isnull(player_join_date))
|
||||
return FALSE
|
||||
|
||||
// This works because ISO-8601 is cool
|
||||
var/grandfather_date = initial(tutorial_type.grandfather_date)
|
||||
if (!isnull(grandfather_date) && player_join_date < grandfather_date)
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Marks the tutorial as completed.
|
||||
/// Call `/datum/tutorial/proc/complete()` instead.
|
||||
/datum/tutorial_manager/proc/complete(mob/user)
|
||||
set waitfor = FALSE
|
||||
|
||||
ASSERT(!isnull(user.ckey))
|
||||
|
||||
finished_ckeys[user.ckey] = TRUE
|
||||
performing_ckeys -= user.ckey
|
||||
|
||||
SSblackbox.record_feedback("tally", "tutorial_completed", 1, "[tutorial_type]")
|
||||
log_game("[key_name(user)] completed the [tutorial_type] tutorial.")
|
||||
|
||||
if (SSdbcore.IsConnected())
|
||||
INVOKE_ASYNC(src, PROC_REF(log_completion_to_database), user.ckey)
|
||||
|
||||
/datum/tutorial_manager/proc/log_completion_to_database(ckey)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
var/datum/db_query/insert_tutorial_query = SSdbcore.NewQuery(
|
||||
"INSERT INTO [format_table_name("tutorial_completions")] (ckey, tutorial_key) VALUES (:ckey, :tutorial_key) ON DUPLICATE KEY UPDATE tutorial_key = tutorial_key",
|
||||
list(
|
||||
"ckey" = ckey,
|
||||
"tutorial_key" = get_key(),
|
||||
)
|
||||
)
|
||||
|
||||
insert_tutorial_query.warn_execute()
|
||||
|
||||
qdel(insert_tutorial_query)
|
||||
|
||||
/// Dismisses the tutorial, not marking it as completed in the database.
|
||||
/// Call `/datum/tutorial/proc/dismiss()` instead.
|
||||
/datum/tutorial_manager/proc/dismiss(mob/user)
|
||||
performing_ckeys -= user.ckey
|
||||
|
||||
/// Given a ckey, will mark them as being completed without affecting the database.
|
||||
/// Call `/datum/tutorial/proc/complete()` instead.
|
||||
/datum/tutorial_manager/proc/mark_as_completed(ckey)
|
||||
finished_ckeys[ckey] = TRUE
|
||||
performing_ckeys -= ckey
|
||||
|
||||
/// Gives the key that will be saved in the database.
|
||||
/// Must be 64 characters or less.
|
||||
/datum/tutorial_manager/proc/get_key()
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
return copytext("[tutorial_type]", length("[/datum/tutorial]") + 2)
|
||||
@@ -0,0 +1,53 @@
|
||||
/atom/movable/screen/tutorial_instruction
|
||||
icon = 'icons/effects/alphacolors.dmi'
|
||||
icon_state = "white"
|
||||
color = COLOR_NEARLY_ALL_BLACK
|
||||
alpha = 0
|
||||
screen_loc = "TOP-2,CENTER"
|
||||
layer = TUTORIAL_INSTRUCTIONS_LAYER
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
|
||||
var/client/client
|
||||
var/atom/movable/screen/tutorial_instruction_text/instruction_text
|
||||
|
||||
/atom/movable/screen/tutorial_instruction/Initialize(mapload, message, client/client)
|
||||
. = ..()
|
||||
|
||||
transform = transform.Scale(36, 2.5)
|
||||
|
||||
src.client = client
|
||||
animate(src, alpha = 245, time = 0.8 SECONDS, easing = SINE_EASING)
|
||||
|
||||
instruction_text = new(src, message, client)
|
||||
vis_contents += instruction_text
|
||||
|
||||
/atom/movable/screen/tutorial_instruction/Destroy()
|
||||
client = null
|
||||
QDEL_NULL(instruction_text)
|
||||
|
||||
return ..()
|
||||
|
||||
/atom/movable/screen/tutorial_instruction/proc/change_message(message)
|
||||
instruction_text.change_message(message)
|
||||
|
||||
/atom/movable/screen/tutorial_instruction_text
|
||||
maptext_height = 480
|
||||
maptext_y = -2
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
layer = TUTORIAL_INSTRUCTIONS_LAYER
|
||||
|
||||
/atom/movable/screen/tutorial_instruction_text/Initialize(mapload, message, client/client)
|
||||
. = ..()
|
||||
|
||||
var/view = client?.view_size.getView()
|
||||
maptext_width = view ? view_to_pixels(view)[1] : 480
|
||||
pixel_x = (maptext_width - world.icon_size) * -0.5
|
||||
|
||||
change_message(message)
|
||||
|
||||
/atom/movable/screen/tutorial_instruction_text/proc/change_message(message)
|
||||
// We don't use MAPTEXT macro here because it doesn't handle big text
|
||||
message = "<span style='font-family: \"VCR OSD Mono\"; font-size: 22px; text-align: center'>[message]</span>"
|
||||
|
||||
animate(src, alpha = 0, time = (maptext ? 0.5 SECONDS : 0), easing = SINE_EASING)
|
||||
animate(alpha = 255, time = 0.5 SECONDS, maptext = message)
|
||||
@@ -0,0 +1,109 @@
|
||||
#define TIME_TO_START_MOVING_DROP_ICON (0.5 SECONDS)
|
||||
|
||||
#define STAGE_DROP_ITEM "STAGE_DROP_ITEM"
|
||||
#define STAGE_PICK_SOMETHING_UP "STAGE_PICK_SOMETHING_UP"
|
||||
|
||||
/// Tutorial for showing how to drop items.
|
||||
/// Fired when clicking on an item with another item with a filled inactive hand.
|
||||
/datum/tutorial/drop
|
||||
grandfather_date = "2023-01-07"
|
||||
|
||||
var/stage = STAGE_DROP_ITEM
|
||||
var/atom/movable/screen/drop_preview
|
||||
var/obj/last_held_item
|
||||
|
||||
/datum/tutorial/drop/Destroy(force, ...)
|
||||
last_held_item = null
|
||||
user.client?.screen -= drop_preview
|
||||
QDEL_NULL(drop_preview)
|
||||
return ..()
|
||||
|
||||
/datum/tutorial/drop/perform(list/params)
|
||||
create_drop_preview(params[SCREEN_LOC])
|
||||
addtimer(CALLBACK(src, PROC_REF(show_instructions)), TIME_TO_START_MOVING_DROP_ICON)
|
||||
|
||||
RegisterSignal(user, COMSIG_MOB_DROPPING_ITEM, PROC_REF(on_dropped_item))
|
||||
RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands))
|
||||
RegisterSignal(user, COMSIG_LIVING_PICKED_UP_ITEM, PROC_REF(on_pick_up_item))
|
||||
|
||||
update_held_item()
|
||||
|
||||
/datum/tutorial/drop/perform_completion_effects_with_delay()
|
||||
UnregisterSignal(user, list(COMSIG_MOB_DROPPING_ITEM, COMSIG_MOB_SWAP_HANDS, COMSIG_LIVING_PICKED_UP_ITEM))
|
||||
if (!isnull(last_held_item))
|
||||
UnregisterSignal(last_held_item, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
return 0
|
||||
|
||||
/datum/tutorial/drop/proc/create_drop_preview(initial_screen_loc)
|
||||
drop_preview = animate_ui_element(
|
||||
"act_drop",
|
||||
initial_screen_loc,
|
||||
ui_drop_throw,
|
||||
TIME_TO_START_MOVING_DROP_ICON,
|
||||
)
|
||||
|
||||
/datum/tutorial/drop/proc/show_instructions()
|
||||
if (QDELETED(src))
|
||||
return
|
||||
|
||||
switch (stage)
|
||||
if (STAGE_DROP_ITEM)
|
||||
show_instruction(keybinding_message(
|
||||
/datum/keybinding/mob/drop_item,
|
||||
"Press '%KEY%' to drop your current item",
|
||||
"Click '<b>DROP</b>' to drop your current item",
|
||||
))
|
||||
if (STAGE_PICK_SOMETHING_UP)
|
||||
show_instruction("Pick something up!")
|
||||
|
||||
/datum/tutorial/drop/proc/on_swap_hands()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (isnull(user.get_active_held_item()))
|
||||
if (stage != STAGE_PICK_SOMETHING_UP)
|
||||
stage = STAGE_PICK_SOMETHING_UP
|
||||
show_instructions()
|
||||
else if (stage == STAGE_PICK_SOMETHING_UP)
|
||||
stage = STAGE_DROP_ITEM
|
||||
show_instructions()
|
||||
|
||||
update_held_item()
|
||||
|
||||
/datum/tutorial/drop/proc/on_dropped_item()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
stage = STAGE_PICK_SOMETHING_UP
|
||||
show_instructions()
|
||||
|
||||
/datum/tutorial/drop/proc/on_pick_up_item()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (stage != STAGE_PICK_SOMETHING_UP)
|
||||
dismiss()
|
||||
return
|
||||
|
||||
complete()
|
||||
|
||||
// Exists so that if we, say, place the item on a table, we don't count that as completion
|
||||
/datum/tutorial/drop/proc/update_held_item()
|
||||
if (!isnull(last_held_item))
|
||||
UnregisterSignal(last_held_item, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
last_held_item = user.get_active_held_item()
|
||||
if (isnull(last_held_item))
|
||||
return
|
||||
|
||||
RegisterSignal(last_held_item, COMSIG_MOVABLE_MOVED, PROC_REF(on_held_item_moved))
|
||||
|
||||
/datum/tutorial/drop/proc/on_held_item_moved()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (stage == STAGE_PICK_SOMETHING_UP)
|
||||
return
|
||||
|
||||
dismiss()
|
||||
|
||||
#undef STAGE_DROP_ITEM
|
||||
#undef STAGE_PICK_SOMETHING_UP
|
||||
#undef TIME_TO_START_MOVING_DROP_ICON
|
||||
@@ -0,0 +1,86 @@
|
||||
#define TIME_TO_START_MOVING_HAND_ICON (0.5 SECONDS)
|
||||
|
||||
#define STAGE_SHOULD_SWAP_HAND "STAGE_SHOULD_SWAP_HAND"
|
||||
#define STAGE_PICK_UP_ITEM "STAGE_PICK_UP_ITEM"
|
||||
|
||||
/// Tutorial for showing how to switch hands.
|
||||
/// Fired when clicking on an item with another item with an empty inactive hand.
|
||||
/datum/tutorial/switch_hands
|
||||
grandfather_date = "2023-01-07"
|
||||
|
||||
var/stage = STAGE_SHOULD_SWAP_HAND
|
||||
var/atom/movable/screen/hand_preview
|
||||
|
||||
// So that they don't just drop the item
|
||||
var/hand_to_watch
|
||||
|
||||
/datum/tutorial/switch_hands/New(mob/user)
|
||||
. = ..()
|
||||
|
||||
hand_to_watch = (user.active_hand_index % user.held_items.len) + 1
|
||||
|
||||
/datum/tutorial/switch_hands/Destroy(force, ...)
|
||||
user.client?.screen -= hand_preview
|
||||
QDEL_NULL(hand_preview)
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/tutorial/switch_hands/perform(list/params)
|
||||
create_hand_preview(params[SCREEN_LOC])
|
||||
addtimer(CALLBACK(src, PROC_REF(show_instructions)), TIME_TO_START_MOVING_HAND_ICON)
|
||||
|
||||
RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands))
|
||||
RegisterSignal(user, COMSIG_LIVING_PICKED_UP_ITEM, PROC_REF(on_pick_up_item))
|
||||
|
||||
/datum/tutorial/switch_hands/perform_completion_effects_with_delay()
|
||||
UnregisterSignal(user, list(COMSIG_MOB_SWAP_HANDS, COMSIG_LIVING_PICKED_UP_ITEM))
|
||||
return 0
|
||||
|
||||
/datum/tutorial/switch_hands/proc/create_hand_preview(initial_screen_loc)
|
||||
hand_preview = animate_ui_element(
|
||||
"hand_[hand_to_watch % 2 == 0 ? "r" : "l"]",
|
||||
initial_screen_loc,
|
||||
ui_hand_position(hand_to_watch),
|
||||
TIME_TO_START_MOVING_HAND_ICON,
|
||||
)
|
||||
|
||||
/datum/tutorial/switch_hands/proc/show_instructions()
|
||||
if (QDELETED(src))
|
||||
return
|
||||
|
||||
switch (stage)
|
||||
if (STAGE_SHOULD_SWAP_HAND)
|
||||
var/hand_name = hand_to_watch % 2 == 0 ? "right" : "left"
|
||||
show_instruction(keybinding_message(
|
||||
/datum/keybinding/mob/swap_hands,
|
||||
"Press '%KEY%' to use your [hand_name] hand",
|
||||
"Click '<b>SWAP</b>' to use your [hand_name] hand",
|
||||
))
|
||||
if (STAGE_PICK_UP_ITEM)
|
||||
show_instruction("Pick something up!")
|
||||
|
||||
/datum/tutorial/switch_hands/proc/on_swap_hands()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (isnull(user.get_active_held_item()))
|
||||
stage = STAGE_PICK_UP_ITEM
|
||||
show_instructions()
|
||||
else if (isnull(user.get_inactive_held_item()))
|
||||
stage = STAGE_SHOULD_SWAP_HAND
|
||||
show_instructions()
|
||||
else
|
||||
// You somehow got an item in both hands during the tutorial without switching hands.
|
||||
// Good job I guess?
|
||||
complete()
|
||||
|
||||
/datum/tutorial/switch_hands/proc/on_pick_up_item()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (user.active_hand_index != hand_to_watch)
|
||||
return
|
||||
|
||||
complete()
|
||||
|
||||
#undef STAGE_PICK_UP_ITEM
|
||||
#undef STAGE_SHOULD_SWAP_HAND
|
||||
#undef TIME_TO_START_MOVING_HAND_ICON
|
||||
Reference in New Issue
Block a user