Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tutorial System #4442

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d69794a
basic tutorial system complete
Zonespace27 Sep 19, 2023
4a37897
improvements
Zonespace27 Sep 19, 2023
681851b
updated .md
Zonespace27 Sep 19, 2023
e7677ff
silent cryopod
Zonespace27 Sep 19, 2023
fdc0207
example tutorial added
Zonespace27 Sep 19, 2023
77d9929
WIP
Zonespace27 Sep 19, 2023
81161cd
(sort of) requested changes
Zonespace27 Sep 19, 2023
eab595b
better menu system
Zonespace27 Sep 19, 2023
9aefc3b
insurance
Zonespace27 Sep 19, 2023
501a26e
loc_from_corner addition
Zonespace27 Sep 19, 2023
52cd01e
basic SS13 tutorial, likely needs expansion
Zonespace27 Sep 20, 2023
076864d
nearly-done intents tutorial
Zonespace27 Sep 20, 2023
8eb3c6a
Mildly improved TGUI, still not done
Zonespace27 Sep 20, 2023
9ca44d7
Merge branch 'tutorial-system' of https://github.com/Zonespace27/cmss…
Zonespace27 Sep 20, 2023
2960762
save system and some cleanup
Zonespace27 Sep 22, 2023
c83b8ae
documentation
Zonespace27 Sep 22, 2023
f179463
Merge branch 'master' of https://github.com/cmss13-devs/cmss13 into t…
Zonespace27 Sep 27, 2023
319456d
mistake
Zonespace27 Sep 27, 2023
c0116c6
wip medical
Zonespace27 Sep 28, 2023
eba9309
whole bunch of junk
Zonespace27 Sep 28, 2023
ffc5893
buncha changes
Zonespace27 Oct 12, 2023
7eacd71
linters stop screaming
Zonespace27 Oct 12, 2023
f71f775
good for tm
Zonespace27 Oct 12, 2023
b23281c
insurance
Zonespace27 Oct 12, 2023
f6cc4d9
ui resize
Zonespace27 Oct 12, 2023
6ade0bd
million changes at once
Zonespace27 Oct 13, 2023
db7e09c
tutorial-fixem
Zonespace27 Oct 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/__DEFINES/access.dm
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ most of them are tied into map-placed objects. This should be reworked in the fu
/// Ancients only
#define ACCESS_YAUTJA_ANCIENT 392

/// Anything in a tutorial sequence that shouldn't be accessed
#define ACCESS_TUTORIAL_LOCKED 998
///Temporary, just so I can flag places I need to change
#define ACCESS_COME_BACK_TO_ME 999

Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/dcs/signals/atom/mob/living/signals_living.dm
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@
/// From /mob/living/Collide(): (atom/A)
#define COMSIG_LIVING_PRE_COLLIDE "living_pre_collide"
#define COMPONENT_LIVING_COLLIDE_HANDLED (1<<0)

/// From /mob/living/proc/do_ghost() : (mob/dead/observer/ghost)
#define COMSIG_LIVING_GHOSTED "living_ghosted"
25 changes: 25 additions & 0 deletions code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,28 @@

/// From /obj/item/proc/pickup() : (obj/item/picked_up)
#define COMSIG_MOB_PICKUP_ITEM "mob_pickup_item"

/// From /obj/item/proc/attack_self() : (obj/item/used)
#define COMSIG_MOB_ITEM_ATTACK_SELF "mob_item_attack_self"

/// From /obj/item/proc/dropped() : (obj/item/dropped)
#define COMSIG_MOB_ITEM_DROPPED "mob_item_dropped"


/// From /obj/item/reagent_container/food/snacks/proc/on_Consume() : (obj/item/reagent_container/food/snacks/eaten_food)
#define COMSIG_MOB_EATEN_SNACK "mob_eaten_snack"

/// From /atom/proc/attackby() : (atom/attacked, obj/item/attacked_with)
#define COMSIG_MOB_PARENT_ATTACKBY "mob_parent_attackby"

/// From /obj/item/weapon/gun/proc/reload_into_chamber() : (obj/item/weapon/gun/empty_gun)
#define COMSIG_MOB_GUN_EMPTY "mob_gun_empty"

/// From /obj/item/weapon/gun/proc/reload() : (obj/item/weapon/gun/reloaded)
#define COMSIG_MOB_RELOADED_GUN "mob_reloaded_gun"

/// From /mob/proc/get_status_tab_items() : (list/status_list)
#define COMSIG_MOB_GET_STATUS_TAB_ITEMS "mob_get_status_tab_items"

/// From /datum/tutorial/proc/update_objective() : (new_objective)
#define COMSIG_MOB_TUTORIAL_UPDATE_OBJECTIVE "mob_tutorial_update_objective"
6 changes: 6 additions & 0 deletions code/__DEFINES/dcs/signals/atom/signals_obj.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@

/// from /obj/proc/afterbuckle()
#define COSMIG_OBJ_AFTER_BUCKLE "signal_obj_after_buckle"

/// from /obj/structure/machinery/cryopod/go_out()
#define COMSIG_CRYOPOD_GO_OUT "cryopod_go_out"

/// from /proc/vendor_successful_vend() : (obj/structure/machinery/cm_vending/vendor, list/itemspec, mob/living/carbon/human/user)
#define COMSIG_VENDOR_SUCCESSFUL_VEND "vendor_successful_vend"
5 changes: 5 additions & 0 deletions code/__DEFINES/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@
#define TRAIT_HARDCORE "t_hardcore"
/// If the mob is able to use the vulture rifle or spotting scope
#define TRAIT_VULTURE_USER "t_vulture_user"
/// If the mob is currently loading a tutorial
#define TRAIT_IN_TUTORIAL "t_IN_TUTORIAL"

// -- ability traits --
/// Xenos with this trait cannot have plasma transfered to them
Expand Down Expand Up @@ -246,6 +248,7 @@ GLOBAL_LIST_INIT(mob_traits, list(
TRAIT_REAGENT_SCANNER,
TRAIT_ABILITY_BURROWED,
TRAIT_VULTURE_USER,
TRAIT_IN_TUTORIAL,
))

/*
Expand Down Expand Up @@ -344,6 +347,8 @@ GLOBAL_LIST(trait_name_map)
#define TRAIT_SOURCE_JOB "t_s_job"
///Status trait forced by staff
#define TRAIT_SOURCE_ADMIN "t_s_admin"
/// Status trait coming from a tutorial
#define TRAIT_SOURCE_TUTORIAL "t_s_tutorials"
///Status trait coming from equipment
#define TRAIT_SOURCE_EQUIPMENT(slot) "t_s_equipment_[slot]"
///Status trait coming from skill
Expand Down
5 changes: 5 additions & 0 deletions code/__DEFINES/tutorial.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#define TUTORIAL_ATOM_FROM_TRACKING(path, varname) var##path/##varname = tracking_atoms[##path]

#define TUTORIAL_CATEGORY_BASE "Base" // Shouldn't be used outside of base types
#define TUTORIAL_CATEGORY_MARINE "Marine"
#define TUTORIAL_CATEGORY_XENO "Xenomorph"
2 changes: 2 additions & 0 deletions code/_onclick/item_attack.dm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/obj/item/proc/attack_self(mob/user)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ITEM_ATTACK_SELF, user)
SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK_SELF, src)

if(flags_item & CAN_DIG_SHRAPNEL && ishuman(user))
dig_out_shrapnel(user)
Expand All @@ -11,6 +12,7 @@
/atom/proc/attackby(obj/item/W, mob/living/user,list/mods)
if(SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, W, user, mods) & COMPONENT_NO_AFTERATTACK)
return TRUE
SEND_SIGNAL(user, COMSIG_MOB_PARENT_ATTACKBY, src, W)
return FALSE

/atom/movable/attackby(obj/item/W, mob/living/user)
Expand Down
25 changes: 25 additions & 0 deletions code/datums/components/tutorial_status.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/datum/component/tutorial_status
dupe_mode = COMPONENT_DUPE_UNIQUE
/// What the mob's current tutorial status is, displayed in the status panel
var/tutorial_status = ""

/datum/component/tutorial_status/Initialize()
. = ..()
if(!ismob(parent))
return COMPONENT_INCOMPATIBLE

/datum/component/tutorial_status/RegisterWithParent()
..()
RegisterSignal(parent, COMSIG_MOB_TUTORIAL_UPDATE_OBJECTIVE, PROC_REF(update_objective))
RegisterSignal(parent, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item))

/datum/component/tutorial_status/proc/update_objective(datum/source, objective_text)
SIGNAL_HANDLER

tutorial_status = objective_text

/datum/component/tutorial_status/proc/get_status_tab_item(datum/source, list/status_tab_items)
SIGNAL_HANDLER

if(tutorial_status)
status_tab_items += "Tutorial Objective: " + tutorial_status
155 changes: 155 additions & 0 deletions code/datums/tutorial/_tutorial.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
GLOBAL_LIST_EMPTY(ongoing_tutorials)

/// A tutorial datum contains a set of instructions for a player tutorial, such as what to spawn, what's scripted to occur, and so on.
/datum/tutorial
/// What the tutorial is called, is player facing
var/name = "Base"
/// Internal ID of the tutorial, kept for save files
var/tutorial_id = "base"
/// What category the tutorial should be under
var/category = TUTORIAL_CATEGORY_BASE
/// Ref to the bottom-left corner tile of the tutorial room
var/turf/bottom_left_corner
/// Ref to the turf reservation for this tutorial
var/datum/turf_reservation/reservation
/// Ref to the player who is doing the tutorial
var/mob/tutorial_mob
/// If the tutorial will be ending soon
var/tutorial_ending = FALSE
/// A dict of type:atom ref for some important junk that should be trackable
var/list/tracking_atoms = list()
/// What map template should be used for the tutorial
var/datum/map_template/tutorial/tutorial_template = /datum/map_template/tutorial

/datum/tutorial/Destroy(force, ...)
if(bottom_left_corner)
var/obj/landmark = locate(/obj/effect/landmark/tutorial_bottom_left) in bottom_left_corner.contents
qdel(landmark)

GLOB.ongoing_tutorials -= src
QDEL_NULL(reservation) // Its Destroy() handles releasing reserved turfs

if(!QDELETED(tutorial_mob))
QDEL_NULL(tutorial_mob)

for(var/path in tracking_atoms)
QDEL_NULL(tracking_atoms[path])

tracking_atoms.Cut()

return ..()

/// The proc to begin doing everything related to the tutorial
/datum/tutorial/proc/start_tutorial(mob/starting_mob)
SHOULD_CALL_PARENT(TRUE)

if(!starting_mob?.client)
return FALSE

ADD_TRAIT(starting_mob, TRAIT_IN_TUTORIAL, TRAIT_SOURCE_TUTORIAL)

tutorial_mob = starting_mob
Zonespace27 marked this conversation as resolved.
Show resolved Hide resolved

reservation = SSmapping.RequestBlockReservation(initial(tutorial_template.width), initial(tutorial_template.height))
if(!reservation)
return FALSE

var/turf/bottom_left_corner_reservation = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3])
var/datum/map_template/tutorial/template = new tutorial_template
template.load(bottom_left_corner_reservation, FALSE, TRUE)

GLOB.ongoing_tutorials |= src
bottom_left_corner = get_turf(locate(/obj/effect/landmark/tutorial_bottom_left) in GLOB.landmarks_list)
var/area/tutorial_area = get_area(bottom_left_corner)
tutorial_area.update_base_lighting() // this will be entirely dark otherwise
init_map()
if(!tutorial_mob)
end_tutorial()

return TRUE

/// The proc used to end and clean up the tutorial
/datum/tutorial/proc/end_tutorial()
SHOULD_CALL_PARENT(TRUE)

if(tutorial_mob)
remove_action(tutorial_mob, /datum/action/tutorial_end)
var/datum/component/status = tutorial_mob.GetComponent(/datum/component/tutorial_status)
qdel(status)
REMOVE_TRAIT(tutorial_mob, TRAIT_IN_TUTORIAL, TRAIT_SOURCE_TUTORIAL)
var/mob/new_player/new_player = new
if(!tutorial_mob.mind)
tutorial_mob.mind_initialize()

tutorial_mob.mind.transfer_to(new_player)

if(!QDELETED(src))
qdel(src)

/datum/tutorial/proc/add_highlight(atom/target, color = "#d19a02")
target.add_filter("tutorial_highlight", 2, list("type" = "outline", "color" = color, "size" = 1))

/datum/tutorial/proc/remove_highlight(atom/target)
target.remove_filter("tutorial_highlight")

/datum/tutorial/proc/add_to_tracking_atoms(atom/reference)
tracking_atoms[reference.type] = reference

/datum/tutorial/proc/remove_from_tracking_atoms(atom/reference)
tracking_atoms -= reference.type

/// Broadcast a message to the player's screen
/datum/tutorial/proc/message_to_player(message)
playsound_client(tutorial_mob.client, 'sound/effects/radiostatic.ogg', tutorial_mob.loc, 25, FALSE)
tutorial_mob.play_screen_text(message, /atom/movable/screen/text/screen_text/command_order/tutorial, rgb(103, 214, 146))

/// Updates a player's objective in their status tab
/datum/tutorial/proc/update_objective(message)
SEND_SIGNAL(tutorial_mob, COMSIG_MOB_TUTORIAL_UPDATE_OBJECTIVE, message)

/// Initialize the tutorial mob.
/datum/tutorial/proc/init_mob()
tutorial_mob.AddComponent(/datum/component/tutorial_status)
give_action(tutorial_mob, /datum/action/tutorial_end, null, null, src)

/// Ends the tutorial after a certain amount of time.
/datum/tutorial/proc/tutorial_end_in(time = 5 SECONDS)
tutorial_ending = TRUE
addtimer(CALLBACK(src, PROC_REF(end_tutorial)), time)

/// Initialize any objects that need to be in the tutorial area from the beginning.
/datum/tutorial/proc/init_map()
return

/datum/action/tutorial_end
name = "Stop Tutorial"
action_icon_state = "hologram_exit"
/// Weakref to the tutorial this is related to
var/datum/weakref/tutorial

/datum/action/tutorial_end/New(Target, override_icon_state, datum/tutorial/selected_tutorial)
. = ..()
tutorial = WEAKREF(selected_tutorial)

/datum/action/tutorial_end/action_activate()
if(!tutorial)
return

var/datum/tutorial/selected_tutorial = tutorial.resolve()
if(selected_tutorial.tutorial_ending)
return

selected_tutorial.end_tutorial()


/datum/map_template/tutorial
name = "Tutorial Zone (12x12)"
mappath = "maps/tutorial/tutorial_12x12.dmm"
width = 12
height = 12

/datum/map_template/tutorial/marine_basic
name = "Tutorial Zone (8x9)"
mappath = "maps/tutorial/tutorial_8x9.dmm"
width = 8
height = 9
71 changes: 71 additions & 0 deletions code/datums/tutorial/_tutorial_menu.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/datum/tutorial_menu
/// Dict of "category" : [name = "name", path = "path"]
var/static/list/categories = list()


/datum/tutorial_menu/New()
if(!length(categories))
var/list/categories_2 = list()
for(var/datum/tutorial/tutorial as anything in subtypesof(/datum/tutorial) - list(/datum/tutorial/marine, /datum/tutorial/marine/example))
if(!(initial(tutorial.category) in categories_2))
categories_2[initial(tutorial.category)] = list()

categories_2[initial(tutorial.category)] += list(list(
"name" = initial(tutorial.name),
"path" = "[tutorial]",
))

for(var/category in categories_2)
categories += list(list(
"name" = category,
"tutorials" = categories_2[category],
))


/datum/tutorial_menu/proc/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "TutorialList")
ui.open()


/datum/tutorial_menu/ui_state(mob/user)
return GLOB.new_player_state


/datum/tutorial_menu/ui_data(mob/user)
var/list/data = list()

return data


/datum/tutorial_menu/ui_static_data(mob/user)
var/list/data = list()

data["tutorial_categories"] = categories

return data


/datum/tutorial_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return

switch(action)
if("select_tutorial")
var/datum/tutorial/path
if(!params["tutorial_path"])
return

path = text2path(params["tutorial_path"])

if(!path || !isnewplayer(usr))
return

if(HAS_TRAIT(usr, TRAIT_IN_TUTORIAL))
to_chat(usr, SPAN_NOTICE("You are currently in a tutorial, or one is loading. Please be patient."))
return

path = new path
path.start_tutorial(usr)
Loading
Loading