diff --git a/code/__HELPERS/hearted.dm b/code/__HELPERS/hearted.dm
new file mode 100644
index 000000000000..1b2c60a25321
--- /dev/null
+++ b/code/__HELPERS/hearted.dm
@@ -0,0 +1,98 @@
+/// Called when the shuttle starts launching back to centcom, polls a few random players who joined the round for commendations
+/datum/controller/subsystem/ticker/proc/poll_hearts()
+ if(!CONFIG_GET(number/commendation_percent_poll))
+ return
+ var/number_to_ask = round(LAZYLEN(GLOB.joined_player_list) * CONFIG_GET(number/commendation_percent_poll)) + rand(0,1)
+
+ if(number_to_ask == 0)
+ message_admins("Not enough eligible players to poll for commendations.")
+ return
+
+ message_admins("Polling [number_to_ask] players for commendations.")
+
+ for(var/i in GLOB.joined_player_list)
+ var/mob/check_mob = get_mob_by_ckey(i)
+ if(!check_mob?.mind || !check_mob.client)
+ continue
+ // maybe some other filters like bans or whatever
+ INVOKE_ASYNC(check_mob, TYPE_PROC_REF(/mob, query_heart), 1)
+ number_to_ask--
+ if(number_to_ask <= 0)
+ break
+
+/// Once the round is actually over, cycle through the ckeys in the hearts list and give them the hearted status
+/datum/controller/subsystem/ticker/proc/handle_hearts()
+ var/list/message = list("The following players were commended this round: ")
+ var/i = 0
+ for(var/hearted_ckey in hearts)
+ i++
+ var/mob/hearted_mob = get_mob_by_ckey(hearted_ckey)
+ if(!hearted_mob?.client)
+ continue
+ hearted_mob.client.adjust_heart()
+ message += "[hearted_ckey][i==hearts.len ? "" : ", "]"
+ message_admins(message.Join())
+
+/// Ask someone if they'd like to award a commendation for the round, 3 tries to get the name they want before we give up
+/mob/proc/query_heart(attempt=1)
+ if(!client || attempt > 3)
+ return
+ if(attempt == 1 && tgui_alert(src, "Was there another character you noticed being kind this round that you would like to anonymously thank?", "<3?", list("Yes", "No"), timeout = 30 SECONDS) != "Yes")
+ return
+
+ var/heart_nominee
+ switch(attempt)
+ if(1)
+ heart_nominee = input(src, "What was their name? Just a first or last name may be enough. (Leave blank to cancel)", "<3?")
+ if(2)
+ heart_nominee = input(src, "Try again, what was their name? Just a first or last name may be enough. (Leave blank to cancel)", "<3?")
+ if(3)
+ heart_nominee = input(src, "One more try, what was their name? Just a first or last name may be enough. (Leave blank to cancel)", "<3?")
+ if(!heart_nominee)
+ return
+
+ heart_nominee = lowertext(heart_nominee)
+ var/list/name_checks = get_mob_by_name(heart_nominee)
+ if(!name_checks || name_checks.len == 0)
+ query_heart(attempt + 1)
+ return
+ name_checks = shuffle(name_checks)
+
+ for(var/i in name_checks)
+ var/mob/heart_contender = i
+ if(heart_contender == src)
+ continue
+
+ switch(tgui_alert(src, "Is this the person: [heart_contender.name] ([heart_contender.real_name])", "<3?", list("Yes!", "Nope", "Cancel"), timeout = 15 SECONDS))
+ if("Yes!")
+ heart_contender.receive_heart(src)
+ return
+ if("Nope")
+ continue
+ else
+ return
+
+ query_heart(attempt + 1)
+
+/*
+* Once we've confirmed who we're commending, either set their status now or log it for the end of the round
+*
+* This used to be reversed, being named nominate_heart and being called on the mob sending the commendation and the first argument being
+* the heart_recepient, but that was confusing and unintuitive, so now src is the person being commended and the sender is now the first argument.
+*
+* Arguments:
+* * heart_sender: The reference to the mob who sent the commendation, just for the purposes of logging
+* * duration: How long from the moment it's applied the heart will last
+* * instant: If TRUE (or if the round is already over), we'll give them the heart status now, if FALSE, we wait until the end of the round (which is the standard behavior)
+*/
+
+/mob/proc/receive_heart(mob/heart_sender, duration = 24 HOURS, instant = FALSE)
+ if(!client)
+ return
+ to_chat(heart_sender, span_nicegreen("Commendation sent!"))
+ message_admins("[key_name(heart_sender)] commended [key_name(src)] [instant ? "(instant)" : ""]")
+ log_admin("[key_name(heart_sender)] commended [key_name(src)] [instant ? "(instant)" : ""]")
+ if(instant || SSticker.current_state == GAME_STATE_FINISHED)
+ client.adjust_heart(duration)
+ else
+ LAZYADD(SSticker.hearts, ckey)
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index 01fd964120ea..32e482bfc35d 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -226,6 +226,11 @@
CHECK_TICK
+ //check config blah blah
+ handle_hearts()
+
+ CHECK_TICK
+
//Now print them all into the log!
log_game("Antagonists at round end were...")
for(var/antag_name in total_antagonists)
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 0cd455d172a6..b9955b4f5cc9 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -382,3 +382,6 @@
max_val = 255
config_entry_value = 127
min_val = 127
+
+/datum/config_entry/number/commendation_percent_poll
+ integer = FALSE
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index a6a3dafd1590..ed1a93c50e08 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -80,6 +80,8 @@ SUBSYSTEM_DEF(shuttle)
jump_timer = addtimer(VARSET_CALLBACK(src, jump_mode, BS_JUMP_COMPLETED), jump_completion_time, TIMER_STOPPABLE)
priority_announce("Jump initiated. ETA: [jump_completion_time / (1 MINUTES)] minutes.", null, null, "Priority")
+ INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker,poll_hearts))
+
/datum/controller/subsystem/shuttle/proc/request_transit_dock(obj/docking_port/mobile/M)
if(!istype(M))
CRASH("[M] is not a mobile docking port")
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 7f0e9c8ee627..2b3ac0619c5a 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -62,6 +62,8 @@ SUBSYSTEM_DEF(ticker)
/// Why an emergency shuttle was called
var/emergency_reason
+ /// People who have been commended and will receive a heart
+ var/list/hearts
/datum/controller/subsystem/ticker/Initialize(timeofday)
load_mode()
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index b62a7830cc0d..a438fa57a066 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -195,6 +195,7 @@
body += "Thunderdome 2"
body += "Thunderdome Admin"
body += "Thunderdome Observer"
+ body += "Commend Behavior | "
body += "
"
body += "