diff --git a/code/datums/medal_awards.dm b/code/datums/medal_awards.dm index ba8847c03661..a3041c622bd6 100644 --- a/code/datums/medal_awards.dm +++ b/code/datums/medal_awards.dm @@ -11,6 +11,7 @@ GLOBAL_LIST_EMPTY(medal_awards) GLOBAL_LIST_EMPTY(jelly_awards) +GLOBAL_LIST_EMPTY(medal_recommendations) /datum/recipient_awards var/list/medal_names @@ -160,7 +161,99 @@ GLOBAL_LIST_INIT(human_medals, list(MARINE_CONDUCT_MEDAL, MARINE_BRONZE_HEART_ME return TRUE -/proc/print_medal(mob/living/carbon/human/user, obj/printer) +/proc/give_medal_award_prefilled(medal_location, mob/giving_mob, chosen_recipient, recipient_rank, recipient_ckey, reason, _medal_type) + var/list/recipient_ranks = list() + for(var/datum/data/record/record in GLOB.data_core.general) + var/recipient_name = record.fields["name"] + recipient_ranks[recipient_name] = record.fields["rank"] + + if(!chosen_recipient) + return FALSE + + // Pick a medal + var/medal_type = _medal_type + if(!medal_type) + return FALSE + + // Write a citation + var/citation = strip_html(reason) + if(!citation) + return FALSE + + // Get mob information + var/posthumous = TRUE + var/mob/recipient_mob + var/found_other = FALSE + + for(var/mob/mob in GLOB.mob_list) + if(mob.real_name == chosen_recipient) + // Recipient: Check if they are dead, and get some info + // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct + if(isliving(mob) && mob.stat != DEAD) + posthumous = FALSE + recipient_mob = mob + if(found_other) + break + found_other = TRUE + if(!recipient_mob) + for(var/mob/mob in GLOB.dead_mob_list) + if(mob.real_name == chosen_recipient) + // Recipient: Check if they are dead?, and get some info + // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct + if(isliving(mob) && mob.stat != DEAD) + posthumous = FALSE + recipient_mob = mob + break + + // Create the recipient_award + if(!GLOB.medal_awards[chosen_recipient]) + GLOB.medal_awards[chosen_recipient] = new /datum/recipient_awards() + var/datum/recipient_awards/recipient_award = GLOB.medal_awards[chosen_recipient] + recipient_award.recipient_rank = recipient_rank + recipient_award.recipient_ckey = recipient_ckey + recipient_award.recipient_mob = recipient_mob + recipient_award.giver_mob += giving_mob + recipient_award.medal_names += medal_type + recipient_award.medal_citations += citation + recipient_award.posthumous += posthumous + recipient_award.giver_ckey += giving_mob.ckey + + recipient_award.giver_rank += recipient_ranks[giving_mob.real_name] // Currently not used in marine award message + recipient_award.giver_name += giving_mob.real_name // Currently not used in marine award message + + // Create an actual medal item + if(medal_location) + var/obj/item/clothing/accessory/medal/medal + switch(medal_type) + if(MARINE_CONDUCT_MEDAL) + medal = new /obj/item/clothing/accessory/medal/bronze/conduct(medal_location) + if(MARINE_BRONZE_HEART_MEDAL) + medal = new /obj/item/clothing/accessory/medal/bronze/heart(medal_location) + if(MARINE_VALOR_MEDAL) + medal = new /obj/item/clothing/accessory/medal/silver/valor(medal_location) + if(MARINE_HEROISM_MEDAL) + medal = new /obj/item/clothing/accessory/medal/gold/heroism(medal_location) + else + return FALSE + medal.recipient_name = chosen_recipient + medal.medal_citation = citation + medal.recipient_rank = recipient_rank + recipient_award.medal_items += medal + else + recipient_award.medal_items += null + + // Recipient: Add the medal to the player's stats + if(recipient_ckey) + var/datum/entity/player_entity/recipient_player = setup_player_entity(recipient_ckey) + if(recipient_player) + recipient_player.track_medal_earned(medal_type, recipient_mob, recipient_rank, citation, giving_mob) + + // Inform staff of success + message_admins("[key_name_admin(giving_mob)] awarded a [medal_type] to [chosen_recipient] for: \'[citation]\'.") + + return TRUE + +/proc/open_medal_panel(mob/living/carbon/human/user, obj/printer) var/obj/item/card/id/card = user.wear_id if(!card) to_chat(user, SPAN_WARNING("You must have an authenticated ID Card to award medals.")) @@ -178,8 +271,9 @@ GLOBAL_LIST_INIT(human_medals, list(MARINE_CONDUCT_MEDAL, MARINE_BRONZE_HEART_ME user.visible_message("ERROR: ID card not registered for [user.real_name] in USCM registry. Potential medal fraud detected.") return - if(give_medal_award(get_turf(printer))) - user.visible_message(SPAN_NOTICE("[printer] prints a medal.")) + GLOB.ic_medals_panel.user_locs[WEAKREF(user)] = WEAKREF(printer) + GLOB.ic_medals_panel.tgui_interact(user) + GLOBAL_LIST_INIT(xeno_medals, list(XENO_SLAUGHTER_MEDAL, XENO_RESILIENCE_MEDAL, XENO_SABOTAGE_MEDAL, XENO_PROLIFERATION_MEDAL, XENO_REJUVENATION_MEDAL)) @@ -364,3 +458,200 @@ GLOBAL_LIST_INIT(xeno_medals, list(XENO_SLAUGHTER_MEDAL, XENO_RESILIENCE_MEDAL, message_admins("[key_name_admin(usr)] deleted [recipient_name]'s [medal_type] for: \'[citation]\'.") return TRUE + +/datum/medal_recommendation + var/recipient_rank + var/recipient_ckey + var/recipient_name + var/recommended_by_name + var/recommended_by_ckey + var/reason + var/recommended_by_rank + + +/proc/add_medal_recommendation(mob/recommendation_giver) + // Pick a marine + var/list/possible_recipients = list() + var/list/recipient_ranks = list() + for(var/datum/data/record/record in GLOB.data_core.general) + var/recipient_name = record.fields["name"] + if(recipient_name == recommendation_giver.real_name) + continue + recipient_ranks[recipient_name] = record.fields["rank"] + possible_recipients += recipient_name + var/chosen_recipient = tgui_input_list(recommendation_giver, "Who do you want to recommend a medal for?", "Medal Recommendation", possible_recipients) + if(!chosen_recipient) + return FALSE + + // Write a citation + var/reason = strip_html(tgui_input_text(recommendation_giver, "Why does this person deserve a medal?", "Medal Recommendation", null, MAX_PAPER_MESSAGE_LEN, TRUE), MAX_PAPER_MESSAGE_LEN) + if(!reason) + return FALSE + + // Get mob information + var/recipient_rank = recipient_ranks[chosen_recipient] + var/recipient_ckey + var/mob/recipient_mob + var/found_other = FALSE + + for(var/mob/mob in GLOB.mob_list) + if(mob.real_name == chosen_recipient) + // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct + recipient_ckey = mob.persistent_ckey + recipient_mob = mob + if(found_other) + break + found_other = TRUE + if(!recipient_mob) + for(var/mob/mob in GLOB.dead_mob_list) + if(mob.real_name == chosen_recipient) + // Recipient: Check if they are dead?, and get some info + // We might not get this info if gibbed, so we'd need to refactor again and find another way if we want stats always correct + recipient_ckey = mob.persistent_ckey + recipient_mob = mob + break + + // Create the recipient_award + var/datum/medal_recommendation/recommendation = new /datum/medal_recommendation() + GLOB.medal_recommendations += recommendation + + recommendation.recipient_rank = recipient_rank + recommendation.recipient_ckey = recipient_ckey + recommendation.recipient_name = recipient_mob.real_name + recommendation.recommended_by_name = recommendation_giver.real_name + recommendation.recommended_by_ckey = recommendation_giver.ckey + recommendation.recommended_by_rank = recipient_ranks[recommendation_giver.real_name] + + + recommendation.reason = reason + + return TRUE + + +GLOBAL_DATUM_INIT(ic_medals_panel, /datum/ic_medal_panel, new) + +/datum/ic_medal_panel + var/name = "Medals Panel" + var/list/datum/weakref/user_locs = list() + +/datum/ic_medal_panel/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "IcMedalsPanel", "Medals Panel") + ui.open() + ui.set_autoupdate(FALSE) + +/datum/ic_medal_panel/ui_state(mob/user) + var/datum/weakref/user_reference = WEAKREF(user) + var/datum/weakref/loc_reference = user_locs[user_reference] + if(istype(loc_reference?.resolve(), /obj/item)) + return GLOB.not_incapacitated_and_inventory_state + else + return GLOB.not_incapacitated_and_adjacent_state + +/datum/ic_medal_panel/ui_host(mob/user) + . = ..() + var/datum/weakref/user_reference = WEAKREF(user) + var/datum/weakref/loc_reference = user_locs[user_reference] + . = loc_reference?.resolve() + +/datum/ic_medal_panel/ui_data(mob/user) + var/list/data = list() + data["recommendations"] = list() + + for(var/datum/medal_recommendation/recommendation in GLOB.medal_recommendations) + var/recommendation_list = list() + + recommendation_list["rank"] = recommendation.recipient_rank + recommendation_list["name"] = recommendation.recipient_name + recommendation_list["ref"] = REF(recommendation) + recommendation_list["recommender_name"] = recommendation.recommended_by_name + recommendation_list["reason"] = recommendation.reason + recommendation_list["recommender_rank"] = recommendation.recommended_by_rank + + data["recommendations"] += list(recommendation_list) + return data + +/datum/ic_medal_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/living/carbon/human/user = usr + var/obj/item/card/id/card = user.wear_id + if(!card) + to_chat(user, SPAN_WARNING("You must have an authenticated ID Card to award medals.")) + return + + if(!((card.paygrade in GLOB.co_paygrades) || (card.paygrade in GLOB.highcom_paygrades))) + to_chat(user, SPAN_WARNING("Only a Senior Officer can award medals!")) + return + + if(!card.registered_ref) + user.visible_message("ERROR: ID card not registered in USCM registry. Potential medal fraud detected.") + return + + var/real_owner_ref = card.registered_ref + + if(real_owner_ref != WEAKREF(user)) + user.visible_message("ERROR: ID card not registered for [user.real_name] in USCM registry. Potential medal fraud detected.") + return + + var/datum/weakref/user_ref = WEAKREF(user) + var/datum/weakref/loc_ref = user_locs[user_ref] + var/atom/actual_loc = loc_ref?.resolve() + if(!actual_loc) + return + + switch(action) + if("grant_new_medal") + if(give_medal_award(get_turf(actual_loc))) + actual_loc.visible_message(SPAN_NOTICE("[actual_loc] prints a medal.")) + . = TRUE + + if("approve_medal") + var/recommendation_ref = params["ref"] + var/medal_type = params["medal_type"] + if(!(medal_type in GLOB.human_medals)) + return + var/datum/medal_recommendation/recommendation = locate(recommendation_ref) in GLOB.medal_recommendations + if(!recommendation) + return + if(recommendation.recipient_name == user.real_name) + to_chat(user, SPAN_WARNING("You cannot give medals to yourself!")) + return + + var/choice = tgui_alert(user, "Would you like to change the medal text?", "Medal Citation", list("Yes", "No")) + var/medal_citation = recommendation.reason + if(choice == "Yes") + medal_citation = strip_html(tgui_input_text(user, "What should the medal citation read?", "Medal Citation", null, MAX_PAPER_MESSAGE_LEN, TRUE), MAX_PAPER_MESSAGE_LEN) + + var/confirm_choice = tgui_alert(user, "Are you sure you want to give a medal to [recommendation.recipient_name]?", "Medal Confirmation", list("Yes", "No")) + if(confirm_choice != "Yes") + return + + if(give_medal_award_prefilled(get_turf(actual_loc), user, recommendation.recipient_name, recommendation.recipient_rank, recommendation.recipient_ckey, medal_citation, medal_type, recommendation.recommended_by_ckey, recommendation.recommended_by_name)) + GLOB.medal_recommendations -= recommendation + qdel(recommendation) + user.visible_message(SPAN_NOTICE("[actual_loc] prints a medal.")) + . = TRUE + + if("deny_medal") + var/recommendation_ref = params["ref"] + var/datum/medal_recommendation/recommendation = locate(recommendation_ref) in GLOB.medal_recommendations + if(!recommendation) + return + var/confirm = tgui_alert(user, "Are you sure you want to deny this medal recommendation?", "Medal Confirmation", list("Yes", "No")) + if(confirm != "Yes") + return + GLOB.medal_recommendations -= recommendation + qdel(recommendation) + . = TRUE + +/datum/ic_medal_panel/ui_close(mob/user) + . = ..() + user_locs -= WEAKREF(user) + +/datum/ic_medal_panel/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/medal) + ) diff --git a/code/game/machinery/computer/almayer_control.dm b/code/game/machinery/computer/almayer_control.dm index e9b969e023b8..d38ccd725785 100644 --- a/code/game/machinery/computer/almayer_control.dm +++ b/code/game/machinery/computer/almayer_control.dm @@ -110,7 +110,7 @@ return switch(action) if("award") - print_medal(usr, src) + open_medal_panel(usr, src) . = TRUE // evac stuff start \\ diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index bcc4c4ac3ec8..77c9bbacc293 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -130,7 +130,7 @@ cooldown_message = world.time if("award") - print_medal(usr, src) + open_medal_panel(usr, src) if("evacuation_start") if(state == STATE_EVACUATION) diff --git a/code/game/machinery/computer/groundside_operations.dm b/code/game/machinery/computer/groundside_operations.dm index 7b4c2d5df771..b5d1c112a3f8 100644 --- a/code/game/machinery/computer/groundside_operations.dm +++ b/code/game/machinery/computer/groundside_operations.dm @@ -235,7 +235,7 @@ log_announcement("[key_name(usr)] has announced the following: [input]") if("award") - print_medal(usr, src) + open_medal_panel(usr, src) if("selectlz") if(SSticker.mode.active_lz) diff --git a/code/game/objects/items/devices/cictablet.dm b/code/game/objects/items/devices/cictablet.dm index 3f87b2bfbea2..4d6db2f7772d 100644 --- a/code/game/objects/items/devices/cictablet.dm +++ b/code/game/objects/items/devices/cictablet.dm @@ -123,7 +123,7 @@ if("award") if(announcement_faction != FACTION_MARINE) return - print_medal(usr, src) + open_medal_panel(usr, src) . = TRUE if("mapview") diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index e5c717e699f0..9581f63679b9 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -405,6 +405,24 @@ var/datum/techtree/T = GET_TREE(TREE_MARINE) T.enter_mob(usr) +/obj/item/device/radio/headset/almayer/verb/give_medal_recommendation() + set name = "Give Medal Recommendation" + set desc = "Send a medal recommendation for approval by the Commanding Officer" + set category = "Object.Medals" + set src in usr + + var/mob/living/carbon/human/wearer = usr + if(!istype(wearer)) + return + var/obj/item/card/id/id_card = wearer.wear_id?.GetID() + if(!istype(id_card)) + return + if(!(id_card.rank in list(JOB_SO, JOB_XO, JOB_SQUAD_LEADER))) + to_chat(wearer, SPAN_WARNING("Only Staff Officers, Executive Officers and Squad Leaders are permitted to give medal recommendations!")) + return + if(add_medal_recommendation(usr)) + to_chat(usr, SPAN_NOTICE("Recommendation successfully submitted.")) + /obj/item/device/radio/headset/almayer/ce name = "chief engineer's headset" desc = "The headset of the guy in charge of spooling engines, managing MTs, and tearing up the floor for scrap metal. Of robust and sturdy construction. Channels are as follows: :n - engineering, :v - marine command, :m - medical, :u - requisitions, :a - alpha squad, :b - bravo squad, :c - charlie squad, :d - delta squad." diff --git a/tgui/packages/tgui/interfaces/IcMedalsPanel.js b/tgui/packages/tgui/interfaces/IcMedalsPanel.js new file mode 100644 index 000000000000..a873ab5368df --- /dev/null +++ b/tgui/packages/tgui/interfaces/IcMedalsPanel.js @@ -0,0 +1,170 @@ +import { useBackend, useLocalState } from '../backend'; +import { Window } from '../layouts'; +import { classes } from 'common/react'; +import { NoticeBox, Section, Box, Button, Stack, Flex } from '../components'; + +export const IcMedalsPanel = (props, context) => { + const { act, data } = useBackend(context); + + const CONDUCT_MEDAL = 'distinguished conduct medal'; + const BRONZE_HEART_MEDAL = 'bronze heart medal'; + const VALOR_MEDAL = 'medal of valor'; + const HEROISM_MEDAL = 'medal of exceptional heroism'; + + const [recommendationMedalTypes, setRecommendationMedalTypes] = useLocalState( + context, + 'recommendation_types', + [] + ); + + return ( + + + + + + {data.recommendations.map((recommendation, index) => ( +
+ + + + + + + + + + + + + + + + + + + + + + + + Recommender: {recommendation.recommender_name} ( + {recommendation.recommender_rank}) + + Reason: {recommendation.reason} + + +
+ ))} +
+
+ ); +};