From 004754a71a08b0a672d562bb8d918d2cfd979e73 Mon Sep 17 00:00:00 2001 From: ynot01 Date: Fri, 2 Sep 2022 14:41:08 -0400 Subject: [PATCH 01/30] objective requesting system --- code/game/gamemodes/objective.dm | 13 +- code/game/gamemodes/objective_items.dm | 6 +- code/modules/admin/topic.dm | 60 +++++++ code/modules/admin/verbs/randomverbs.dm | 52 ++++++ code/modules/paperwork/folders.dm | 167 ++++++++++++++++++ code/modules/uplink/uplink_items.dm | 66 ++++++- .../tgui/interfaces/FolderObjective.js | 40 +++++ 7 files changed, 395 insertions(+), 9 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/FolderObjective.js diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index 96e3ee14accf..64d507f684b4 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -1005,6 +1005,8 @@ GLOBAL_LIST_EMPTY(possible_items_special) /datum/objective/destroy/find_target(dupe_search_range, blacklist) var/list/possible_targets = active_ais(1) + if(LAZYLEN(possible_targets) == 0) + return var/mob/living/silicon/ai/target_ai = pick(possible_targets) target = target_ai.mind update_explanation_text() @@ -1282,7 +1284,7 @@ GLOBAL_LIST_EMPTY(possible_items_special) * Kill Pet */ /datum/objective/minor/pet - name = "assasinate-pet" + name = "assassinate-pet" explanation_text = "Assassinate the HoP's assistant, Ian." /// Pet var/mob/living/pet @@ -1305,12 +1307,12 @@ GLOBAL_LIST_EMPTY(possible_items_special) var/mob/A = locate(P) in GLOB.mob_living_list if(A && is_station_level(A.z)) possible_pets += P - if(!possible_pets) + if(!possible_pets || LAZYLEN(possible_pets) == 0) return var/chosen_pet = rand(1, possible_pets.len) pet = locate(possible_pets[chosen_pet]) in GLOB.mob_living_list - name = "Kill [pet.name]]" - explanation_text = "Assasinate the important animal, [pet.name]" + name = "Kill [pet.name]" + explanation_text = "Assassinate the important animal, [pet.name]" return pet /datum/objective/minor/pet/admin_edit(mob/admin) @@ -1329,7 +1331,8 @@ GLOBAL_LIST_EMPTY(possible_items_special) update_explanation_text() /datum/objective/minor/pet/update_explanation_text() - explanation_text = "Assassinate the important animal, [pet.name]" + if(pet?.name) + explanation_text = "Assassinate the important animal, [pet.name]" /datum/objective/minor/pet/copy_target(datum/objective/minor/pet/old_obj) . = ..() diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index a0a848dda8b8..b30b032cbd95 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -29,7 +29,7 @@ /datum/objective_item/steal/caplaser name = "the captain's antique laser gun." targetitem = /obj/item/gun/energy/laser/captain - difficulty = 5 + difficulty = 10 excludefromjob = list("Captain") /datum/objective_item/steal/hoslaser @@ -47,7 +47,7 @@ /datum/objective_item/steal/jetpack name = "the Captain's jetpack." targetitem = /obj/item/tank/jetpack/oxygen/captain - difficulty = 5 + difficulty = 10 excludefromjob = list("Captain") /datum/objective_item/steal/magboots @@ -71,7 +71,7 @@ /datum/objective_item/steal/nukedisc name = "the nuclear authentication disk." targetitem = /obj/item/disk/nuclear - difficulty = 5 + difficulty = 15 excludefromjob = list("Captain") /datum/objective_item/steal/nukedisc/check_special_completion(obj/item/disk/nuclear/N) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index f937635e5446..f743e6960c06 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -2278,6 +2278,66 @@ to_chat(src.owner, span_danger("Unable to locate fax!")) return owner.send_admin_fax(F) + + else if(href_list["uplink_custom_obj"]) + if(!check_rights(R_ADMIN)) + return + var/datum/uplink_item/new_objective/UPL = locate(href_list["uplink_custom_obj"]) + var/mob/requester = locate(href_list["requester"]) in GLOB.mob_list + if(!requester) + to_chat(usr, span_danger("Requesting mob doesn't exist anymore!")) + return + if(!UPL) + to_chat(usr, span_danger("NewObjective datum doesn't exist anymore!")) + return + if(UPL.admin_forging) + to_chat(usr, span_danger("Another admin is already forging an objective for this request!")) + return + if(UPL.obj_set) + to_chat(usr, span_danger("An objective has already been set for this request!")) + return + usr.client.uplink_custom_obj(UPL, requester) + + else if(href_list["uplink_custom_obj_accept"]) + if(!check_rights(R_ADMIN)) + return + var/obj/item/folder/objective/FLD = locate(href_list["uplink_custom_obj_accept"]) + var/mob/requester = locate(href_list["requester"]) in GLOB.mob_list + if(!FLD) + to_chat(usr, span_danger("Objective Folder does not exist!")) + return + if(!requester) + to_chat(usr, span_danger("Requesting mob doesn't exist anymore!")) + return + if(!FLD.objective) + to_chat(usr, span_danger("Objective Folder has no objective!")) + return + if(!FLD.objective.completed) + to_chat(usr, span_danger("Objective is already marked complete by another admin!")) + return + FLD.objective.completed = TRUE + to_chat(requester, span_notice("The folder lets out a small beep, letting you know that its objective has been marked as complete.")) + message_admins("[key_name_admin(usr)] has marked the custom objective, [FLD.objective.explanation_text], as complete.") + + else if(href_list["uplink_custom_obj_deny"]) + if(!check_rights(R_ADMIN)) + return + var/obj/item/folder/objective/FLD = locate(href_list["uplink_custom_obj_accept"]) + var/mob/requester = locate(href_list["requester"]) in GLOB.mob_list + if(!FLD) + to_chat(usr, span_danger("Objective Folder does not exist!")) + return + if(!requester) + to_chat(usr, span_danger("Requesting mob doesn't exist anymore!")) + return + if(!FLD.objective) + to_chat(usr, span_danger("Objective Folder has no objective!")) + return + if(!FLD.objective.completed) + to_chat(usr, span_danger("Objective is already marked complete by another admin!")) + return + to_chat(requester, span_danger("The folder lets out a harsh beep, letting you know that its objective has not been completed.")) + message_admins("[key_name_admin(usr)] has marked the custom objective, [FLD.objective.explanation_text], as incomplete.") /client/proc/send_global_fax() set category = "Admin.Round Interaction" diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 783f6bc150d2..c0e42c239858 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -1521,3 +1521,55 @@ Traitors and the like can also be revived with the previous role mostly intact. return else usr.forceMove(T) + +/client/proc/uplink_custom_obj(var/datum/uplink_item/new_objective/objective_uplink_datum, mob/requester) + if(!check_rights(R_ADMIN)) + return + + var/obj_txt = "Kill everyone." + obj_txt = stripped_input(usr, "Custom objective:", "Objective", obj_txt) + if(!obj_txt) + objective_uplink_datum.admin_forging = FALSE + message_admins("[key_name_admin(usr)] decided not to forge a custom objective.") + objective_uplink_datum.cancelled(requester) + return + + var/difficulty = 1 + difficulty = input("1-3", "Set Difficulty Rating (Determines TC)", difficulty) as null|num + if(!difficulty || difficulty < 1 || difficulty > 3) + objective_uplink_datum.admin_forging = FALSE + if(difficulty < 1 || difficulty > 3) + to_chat(usr, span_danger("Difficulty must be 1, 2, or 3!")) + message_admins("[key_name_admin(usr)] decided not to forge a custom objective.") + objective_uplink_datum.cancelled(requester) + return + + if(!objective_uplink_datum) + objective_uplink_datum.admin_forging = FALSE + to_chat(usr, span_danger("Requesting uplink item no longer exists!")) + message_admins("[key_name_admin(usr)] decided not to forge a custom objective.") + objective_uplink_datum.cancelled(requester) + return + + if(!requester) + objective_uplink_datum.admin_forging = FALSE + to_chat(usr, span_danger("Requesting mob no longer exists!")) + message_admins("[key_name_admin(usr)] decided not to forge a custom objective.") + objective_uplink_datum.cancelled(requester) + return + + var/datum/objective/custom/forged_objective = new /datum/objective/custom + forged_objective.explanation_text = obj_txt + + var/obj/item/folder/objective/folder = objective_uplink_datum.spawn_objective(requester, forged_objective, difficulty) + + var/diff_txt = list("RANDOM", "EASY", "MEDIUM", "HARD")[difficulty+1] + objective_uplink_datum.admin_forging = FALSE + if(folder) + deltimer(objective_uplink_datum.timer) + message_admins("[key_name_admin(usr)] forged a folder objective for [ADMIN_LOOKUPFLW(requester)] with difficulty rating [diff_txt]: [obj_txt]") + log_game("[key_name(usr)] forged a folder objective for [key_name(requester)] with difficulty rating [diff_txt]: [obj_txt]") + else + to_chat(usr, span_danger("Failed to create objective folder!")) + message_admins("[key_name_admin(usr)] attempted to forge a folder objective for [ADMIN_LOOKUPFLW(requester)] with difficulty rating [diff_txt]: [obj_txt], but it failed to create.") + log_game("[key_name(usr)] attempted to forge a folder objective for [key_name(requester)] with difficulty rating [diff_txt]: [obj_txt], but it failed to create.") diff --git a/code/modules/paperwork/folders.dm b/code/modules/paperwork/folders.dm index 7bc395bdd982..513c60ba098a 100644 --- a/code/modules/paperwork/folders.dm +++ b/code/modules/paperwork/folders.dm @@ -130,3 +130,170 @@ . = ..() new /obj/item/documents/syndicate/mining(src) update_icon() + +/// For traitors: New objective +/obj/item/folder/objective + var/datum/objective/objective // Object not typepath + var/difficulty = 0 + var/tc = 0 + var/admin_msg = FALSE + // Steal objectives initialized later + var/list/easy_objectives = list( + new /datum/objective/assassinate/once, // Kill someone once + new /datum/objective/download, // Download research nodes + new /datum/objective/minor/pet, // Kill a pet + ) + var/list/med_objectives = list() + var/list/hard_objectives = list( + new /datum/objective/destroy, // Kill AI + ) + +/obj/item/folder/objective/Initialize(mapload, _user, _obj, _diff) + . = ..() + + init_steal_objs() + + difficulty = _diff ? _diff : rand(1,3) + tc = clamp(difficulty * 3 + rand(-1,1), 2, 10) // Redundant clamp, but useful if someone wants to adjust the rand + forge_objective(_obj) + + var/folder_type = rand(1,5) + var/folder_color = "gray" + switch(folder_type) + if(2) + desc = "A blue folder." + icon_state = "folder_blue" + folder_color = "blue" + if(3) + desc = "A red folder." + icon_state = "folder_red" + folder_color = "red" + if(4) + desc = "A yellow folder." + icon_state = "folder_yellow" + folder_color = "yellow" + if(5) + desc = "A white folder." + icon_state = "folder_white" + folder_color = "white" + update_icon() + if(_user) + to_chat(_user, span_notice("Your objective has been curated. You will find it as a [folder_color] folder in [get_area_name(src, TRUE)].")) + +/// Initialize steal objectives based on difficulty +/obj/item/folder/objective/proc/init_steal_objs() + for(var/I in subtypesof(/datum/objective_item/steal)) + var/datum/objective/steal/newsteal = new /datum/objective/steal + var/datum/objective_item/steal/S = new I + if(!S.TargetExists()) + continue + if(LAZYLEN(S.special_equipment) > 0) // No special equipment allowed + continue + newsteal.set_target(S) + if(S.difficulty < 5) + easy_objectives += newsteal + else if(S.difficulty >= 5 && S.difficulty < 10) + med_objectives += newsteal + else + hard_objectives += newsteal + +/obj/item/folder/objective/proc/forge_objective(_obj) + if(_obj) + objective = _obj + else + var/list/potential_objectives = list(easy_objectives, med_objectives, hard_objectives)[difficulty] + var/inf_protection = 0 + // This will cycle through invalid objectives + while(!objective || objective.explanation_text == "Nothing." || objective.explanation_text == "Free Objective") + inf_protection++ + if(inf_protection >= 20) + break + + if(objective) + potential_objectives.Remove(objective) + qdel(objective) + + if(LAZYLEN(potential_objectives) <= 0) + break + + objective = pick(potential_objectives) + objective.find_target() + + // i hate objective code so much WHO WROTE THIS???? + if(istype(objective, /datum/objective/download)) + var/datum/objective/download/O = objective + O.gen_amount_goal() + + if(istype(objective, /datum/objective/minor)) + var/datum/objective/minor/O = objective + O.finalize() + + objective.update_explanation_text() + + if(LAZYLEN(potential_objectives) <= 0 || inf_protection >= 20) + qdel(src) + CRASH("No valid [list("EASY", "MEDIUM", "HARD")[difficulty]] objective could be chosen! Deleting folder!") + + // Cleanup + for(var/datum/objective/O in easy_objectives) + easy_objectives.Remove(O) + if(O != objective) + qdel(O) + for(var/datum/objective/O in med_objectives) + med_objectives.Remove(O) + if(O != objective) + qdel(O) + for(var/datum/objective/O in hard_objectives) + hard_objectives.Remove(O) + if(O != objective) + qdel(O) + +/obj/item/folder/objective/attack_self(mob/user) + . = ..() + if(is_syndicate(user)) + ui_interact(user) + objective.owner = user.mind + +/obj/item/folder/objective/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + // Open UI + admin_msg = FALSE + ui = new(user, src, "FolderObjective") + ui.open() + +/obj/item/folder/objective/ui_static_data(mob/user) + . = ..() + .["tc"] = tc || "0" + .["difficulty"] = list("EASY", "MEDIUM", "HARD")[difficulty] + .["objective_text"] = objective?.explanation_text + .["admin_msg"] = admin_msg + +/obj/item/folder/objective/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("check_done") + if(objective.check_completion()) + to_chat(usr, span_notice("NOTICE: OBJECTIVE COMPLETE. GOOD WORK AGENT. DISPENSING REWARD.")) + to_chat(usr, "\The [src] suddenly transforms into [tc] telecrystal[tc == 1 ? "" : "s"]!") + usr.playsound_local(loc, 'sound/machines/ping.ogg', 20, 0) + var/obj/item/stack/telecrystal/reward = new /obj/item/stack/telecrystal + reward.amount = tc + dropped(usr, TRUE) + usr.put_in_hands(reward) + qdel(src) + return TRUE + else if(istype(objective, /datum/objective/custom)) + admin_msg = TRUE + message_admins("[ADMIN_LOOKUPFLW(usr)] has requested an admin objective be checked for completion ([objective.explanation_text]). (MARK COMPLETED) (MARK INCOMPLETE)") + to_chat(usr, span_danger("NOTICE: SENT OBJECTIVE STATUS TO COMMAND FOR REVIEW.")) + return TRUE + else + to_chat(usr, span_danger("ERR: OBJECTIVE NOT COMPLETE")) + usr.playsound(loc, 'sound/machines/buzz-two.ogg', 20, 0) + return TRUE + + diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index 9836f7c5d42e..480e3f3239ed 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -22,7 +22,8 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) continue if (I.restricted && !allow_restricted) continue - + if(istype(I, /datum/uplink_item/new_objective) && prob(50)) + continue if(!filtered_uplink_items[I.category]) filtered_uplink_items[I.category] = list() filtered_uplink_items[I.category][I.name] = I @@ -2314,3 +2315,66 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) item = /obj/item/stamp/syndiround cost = 1 illegal_tech = FALSE + +/datum/uplink_item/new_objective + name = "Request Objective (RANDOM)" + category = "Request Objectives" + desc = "Sends a single-use signal to your employers, where they will determine your new objective \ + and send down a folder to a random location containing your new instructions. \ + Upon completion, you will be rewarded depending on the difficulty of the objective." + item = /obj/item/folder/red + cost = 0 + surplus = 0 + limited_stock = 1 + cant_discount = TRUE + var/difficulty = 0 // 0 is random, then 1-3 + var/timer // Timer to delete if admin forges objective + var/admin_forging = FALSE // If an admin is currently forging an objective + var/set_on_noforge = FALSE // If the timer expired, but is waiting for an admin to cancel forging + var/obj_set = FALSE // If the objective has been set + +/datum/uplink_item/new_objective/easy + name = "Request Objective (EASY)" + difficulty = 1 + desc = "" + +/datum/uplink_item/new_objective/medium + name = "Request Objective (MEDIUM)" + difficulty = 2 + desc = "" + +/datum/uplink_item/new_objective/hard + name = "Request Objective (HARD)" + difficulty = 3 + desc = "" + +/datum/uplink_item/new_objective/spawn_item(spawn_path, mob/user, datum/component/uplink/U) + to_chat(user, span_notice("Signal sent to command. Awaiting response (ETA ~1 minute)...")) + var/diff_txt = list("RANDOM", "EASY", "MEDIUM", "HARD")[difficulty+1] + message_admins("[ADMIN_LOOKUPFLW(user)] has requested an objective ([diff_txt]). (FORGE CUSTOM OBJECTIVE?) (AUTO SET IN 1 MINUTE)") + timer = addtimer(CALLBACK(src, .proc/spawn_objective, user), 1 MINUTES, TIMER_STOPPABLE) + +/datum/uplink_item/new_objective/proc/spawn_objective(user, _obj, _diff) + if(admin_forging) // If the timer expired while an admin was editing it + set_on_noforge = TRUE // Set a random objective if the admin decides not to set one + return + var/new_diff = _diff ? _diff : difficulty + obj_set = TRUE + var/turf/open/floor/F + var/can_see = TRUE + var/see_loops = 0 // infinite loop protection + + while(can_see && see_loops < 15) + F = find_safe_turf(dense_atoms = FALSE) + for(var/mob/living/M in view(13, F)) + if(M.client) + can_see = TRUE + break + can_see = FALSE + see_loops++ + + return new /obj/item/folder/objective(F, user, _obj, new_diff) + +/datum/uplink_item/new_objective/proc/cancelled(user) + if(set_on_noforge) + spawn_objective(user) diff --git a/tgui/packages/tgui/interfaces/FolderObjective.js b/tgui/packages/tgui/interfaces/FolderObjective.js new file mode 100644 index 000000000000..2f669e1c1125 --- /dev/null +++ b/tgui/packages/tgui/interfaces/FolderObjective.js @@ -0,0 +1,40 @@ +import { useBackend } from '../backend'; +import { Button, LabeledList, NoticeBox, Section } from '../components'; +import { Window } from '../layouts'; + +export const FolderObjective = (props, context) => { + const { act, data } = useBackend(context); + const { + tc, + difficulty, + objective_text, + admin_msg + } = data; + return ( + + +
+ + + + {objective_text} + +
+ {admin_msg ? ( + + Your objective status is being reviewed by command. + Please try again later. + + ) : ""} +