diff --git a/_maps/map_files/daftmarsh/daftmarsh.dmm b/_maps/map_files/daftmarsh/daftmarsh.dmm index 8658fb30ee2..99ee51a2bf5 100644 --- a/_maps/map_files/daftmarsh/daftmarsh.dmm +++ b/_maps/map_files/daftmarsh/daftmarsh.dmm @@ -3169,6 +3169,13 @@ }, /turf/open/floor/ruinedwood/spiral, /area/outdoors/town) +"crT" = ( +/obj/structure/fluff/railing/wood{ + dir = 4 + }, +/obj/structure/mercstatue, +/turf/open/floor/twig, +/area/outdoors/town) "cso" = ( /turf/open/floor/grass/yel, /area/outdoors/exposed/church) @@ -9512,6 +9519,10 @@ }, /turf/closed/wall/mineral/stone/moss, /area/under/town/basement) +"hCH" = ( +/obj/structure/mercstatue, +/turf/open/floor/grass, +/area/outdoors/town/keep) "hCR" = ( /obj/structure/fluff/walldeco/church/line{ dir = 8 @@ -127081,7 +127092,7 @@ xuJ oQv iHi dcH -bYV +crT ffK lfe owc @@ -130676,7 +130687,7 @@ eUn ttL ttL iiV -vVc +hCH tea tea tea diff --git a/_maps/map_files/vanderlin/vanderlin.dmm b/_maps/map_files/vanderlin/vanderlin.dmm index 0aa51839907..a542a8de527 100644 --- a/_maps/map_files/vanderlin/vanderlin.dmm +++ b/_maps/map_files/vanderlin/vanderlin.dmm @@ -37,6 +37,7 @@ /obj/structure/fluff/walldeco/customflag{ pixel_x = -32 }, +/obj/structure/mercstatue, /turf/open/floor/grass/red, /area/outdoors/woods_safe) "aaX" = ( @@ -2597,6 +2598,16 @@ "bfG" = ( /turf/closed/wall/mineral/decowood, /area/under/town/basement) +"bfN" = ( +/obj/structure/fluff/railing/wood{ + dir = 1; + layer = 2.7; + pixel_x = 2; + pixel_y = 7 + }, +/obj/structure/mercstatue, +/turf/open/floor/cobble, +/area/outdoors/town) "bfO" = ( /obj/machinery/light/fueled/torchholder/l{ dir = 1 @@ -25427,6 +25438,13 @@ /obj/structure/bed/shit, /turf/open/floor/greenstone, /area/under/town/sewer) +"kCm" = ( +/obj/machinery/light/fueled/torchholder{ + dir = 4 + }, +/obj/structure/mercstatue, +/turf/open/floor/dirt/road, +/area/outdoors/town) "kCM" = ( /obj/effect/landmark/start/vagrant, /turf/open/floor/dirt, @@ -26906,6 +26924,10 @@ }, /turf/open/floor/ruinedwood/spiral, /area/indoors/town/keep/library) +"lht" = ( +/obj/structure/mercstatue, +/turf/open/floor/dirt/road, +/area/outdoors/beach) "lhy" = ( /obj/structure/fluff/railing/border{ dir = 8 @@ -33767,6 +33789,10 @@ /obj/item/clothing/face/shepherd/clothmask, /turf/open/floor/tile, /area/indoors/town/keep/courtagent) +"oaS" = ( +/obj/structure/mercstatue, +/turf/open/floor/dirt/road, +/area/outdoors/town/keep) "oba" = ( /turf/open/floor/cobble, /area/indoors/dungeon) @@ -45714,6 +45740,7 @@ /obj/effect/decal/borderfall{ dir = 1 }, +/obj/structure/mercstatue, /turf/open/floor/blocks, /area/outdoors/town) "sKG" = ( @@ -51780,6 +51807,7 @@ /area/indoors/town/shop) "vjZ" = ( /obj/structure/fake_machine/hailerboard/r, +/obj/structure/mercstatue, /turf/open/floor/tile/brownbrick, /area/indoors/town/merc_guild) "vkc" = ( @@ -67718,7 +67746,7 @@ pao uvp mIW nTH -qAA +lht npN npN qAA @@ -113820,7 +113848,7 @@ oWd mFi kwJ mFi -cJK +kCm kwJ eYe xRO @@ -118829,7 +118857,7 @@ qFx qVe weQ oho -xTl +oaS xTl vqf viN @@ -124992,7 +125020,7 @@ wmF wrm wrm wrm -vbp +bfN mFi hZn hZn diff --git a/_maps/map_files/whitepalacepass/WhitePalacePass.dmm b/_maps/map_files/whitepalacepass/WhitePalacePass.dmm index 70e3aa5cfbc..9df2b99cb85 100644 --- a/_maps/map_files/whitepalacepass/WhitePalacePass.dmm +++ b/_maps/map_files/whitepalacepass/WhitePalacePass.dmm @@ -14563,6 +14563,10 @@ /obj/machinery/light/fueled/wallfire/candle/skull/l, /turf/open/floor/dirt, /area/under/cavewet) +"iNM" = ( +/obj/structure/mercstatue, +/turf/open/floor/cobble, +/area/indoors/town/merc_guild) "iNV" = ( /obj/structure/soil/mushmound, /obj/machinery/light/fueled/wallfire/candle/lamp, @@ -38430,6 +38434,10 @@ /obj/item/natural/melded/t5, /turf/open/floor/tile/masonic/spiral, /area/under/town/basement) +"ybs" = ( +/obj/structure/mercstatue, +/turf/open/floor/grass/cold, +/area/outdoors/town/keep) "ybJ" = ( /obj/structure/bars, /turf/open/floor/cobblerock, @@ -59990,7 +59998,7 @@ qPf jxh vhP vhP -vhP +iNM gBj tnl dIH @@ -180521,7 +180529,7 @@ aYU ewa aYU ewa -tck +ybs tck gLm rZA diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 49dc1590d05..263e498e821 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -34,6 +34,9 @@ GLOBAL_LIST_EMPTY(spidermobs) //all sentient spider mobs GLOBAL_LIST_EMPTY(bots_list) GLOBAL_LIST_EMPTY(aiEyes) +GLOBAL_LIST_EMPTY(available_mercenaries) +GLOBAL_LIST_EMPTY(contracted_mercenaries) + GLOBAL_LIST_EMPTY(language_datum_instances) GLOBAL_LIST_EMPTY(all_languages) diff --git a/code/game/objects/items/mercenary_ring.dm b/code/game/objects/items/mercenary_ring.dm new file mode 100644 index 00000000000..c2f25e9cd71 --- /dev/null +++ b/code/game/objects/items/mercenary_ring.dm @@ -0,0 +1,140 @@ +/obj/item/mercenary_ring + name = "mercenary ring" + desc = "A beautiful golden ring, which resonates in a mercenary's mind when twisted. Usually given to their employers as a way of communication. Only a mercenary may bind themselves to such ring." + icon = 'icons/roguetown/clothing/rings.dmi' + icon_state = "g_newring_topaz" + slot_flags = ITEM_SLOT_RING + sellprice = 1 + dyeable = FALSE + detail_tag = null + detail_color = null + dropshrink = 0.7 + grid_height = 32 + grid_width = 32 + item_weight = 60 GRAMS + + var/datum/weakref/mob_ref + + COOLDOWN_DECLARE(ring_bell) + var/cooldown = 3 MINUTES + var/hear_distance = 100 + +/obj/item/mercenary_ring/Initialize(mapload) + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_HUMAN_ENTER_CRYO, PROC_REF(remove_mercenary)) + +/obj/item/mercenary_ring/Destroy() + mob_ref = null + . = ..() + +/obj/item/mercenary_ring/examine(mob/user) + . = ..() + if(!mob_ref) + to_chat(user, span_warning("There is no mercenary bound to the ring.")) + return + + var/mob/living/carbon/human/bound_merc = mob_ref.resolve() + . += span_notice("The ring is bound to [bound_merc.real_name].") + +/obj/item/mercenary_ring/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + + if(!ishuman(interacting_with)) + return NONE + + var/mob/living/carbon/human/H = interacting_with + + if(!H.mind) + return ITEM_INTERACT_BLOCKING + + if(mob_ref) + to_chat(user, span_warning("It can hold no more minds.")) + return ITEM_INTERACT_BLOCKING + + if(!(user == H)) + to_chat(user, span_warning("I can only bind myself to the ring.")) + return + if(!is_mercenary_job(user.mind.assigned_role)) + to_chat(user, span_warning("I am not a mercenary, the ring does not answer to me.")) + return + + if(IS_DEADITE(H)) + to_chat(user, span_warning("The deadite curse resists the bell's charm.")) + else + add_mercenary(H) + to_chat(user, span_smallnotice("I bind myself to the ring.")) + + playsound(src, 'sound/items/servant_bell.ogg', 80, TRUE) + return ITEM_INTERACT_SUCCESS + +/obj/item/mercenary_ring/attack_self(mob/living/user, list/modifiers) + . = ..() + if(COOLDOWN_FINISHED(src, ring_bell)) + ring_bell(user) + COOLDOWN_START(src, ring_bell, cooldown) + else + playsound(src, 'sound/items/servant_bell.ogg', 80, TRUE) + +/obj/item/mercenary_ring/proc/ring_bell(mob/living/user) + if(!mob_ref) + return + var/mob/living/carbon/human/player = mob_ref.resolve() + if(!player) + return + if(!player.client) + return + if(player.stat >= DEAD) + return + if(HAS_TRAIT(player, TRAIT_DEAF)) + return + if(player.can_block_magic(MAGIC_RESISTANCE_MIND, 0)) + return + var/turf/origin_turf = get_turf(src) + if(!is_in_zweb(player.z, origin_turf.z)) + return + var/distance = get_dist(player, origin_turf) + if(distance > hear_distance) + return + + user.visible_message("[user] twists the mercenary ring.") + playsound(src, 'sound/items/servant_bell.ogg', 100, TRUE) + + player.apply_status_effect(/datum/status_effect/signal_horn/mercenary_ring, null, origin_turf) + var/dirText = "" + var/z_dist = origin_turf.z - player.z + if(z_dist != 0) + var/abs_z = abs(z_dist) + switch(abs_z) + if(1) + dirText += " one story" + if(2) + dirText += " two stories" + else + dirText += " far" + dirText += z_dist > 0 ? " above me" : " below me" + to_chat(player, span_warning("The mercenary ring resonates[dirText].")) + if(distance <= 7) + return + //sound played for other players, by fem_tanyl !!!1!! + player.playsound_local(get_turf(player), 'sound/items/servant_bell.ogg', 35, FALSE, pressure_affected = FALSE) + +/obj/item/mercenary_ring/proc/add_mercenary(mob/living/carbon/human/H) + if(mob_ref) + return + mob_ref = WEAKREF(H) + +/obj/item/mercenary_ring/proc/remove_mercenary(datum/source, mob/living/carbon/human/H) + if(!mob_ref || !ishuman(H)) + return + var/mob/living/carbon/human/bound_mob = mob_ref.resolve() + if(!bound_mob || bound_mob == H) + mob_ref = null + +/datum/status_effect/signal_horn/mercenary_ring + id = "mercenary ring indicator" + duration = 25 SECONDS + alert_type = /atom/movable/screen/alert/status_effect/mercenary_ring + +/atom/movable/screen/alert/status_effect/mercenary_ring + name = "Mercenary Ring" + desc = "I've been summoned by the ring." + icon_state = "mercenary_ring" diff --git a/code/game/objects/items/servant_bell.dm b/code/game/objects/items/servant_bell.dm index 5c5fb95ffd1..984aca23cb7 100644 --- a/code/game/objects/items/servant_bell.dm +++ b/code/game/objects/items/servant_bell.dm @@ -64,6 +64,7 @@ return ITEM_INTERACT_BLOCKING var/mob/living/carbon/human/H = interacting_with + if(!H.mind) return ITEM_INTERACT_BLOCKING @@ -243,3 +244,4 @@ name = "Servant Bell" desc = "I've been summoned by the bell." icon_state = "servant_bell" + diff --git a/code/game/objects/structures/mercstatue.dm b/code/game/objects/structures/mercstatue.dm new file mode 100644 index 00000000000..d0856783be8 --- /dev/null +++ b/code/game/objects/structures/mercstatue.dm @@ -0,0 +1,141 @@ +/obj/structure/mercstatue + name = "mercenary statue" + desc = "A bronze statue of a mercenary from ages long past." + icon = 'icons/roguetown/misc/machines.dmi' + icon_state = "mercstatue" + density = TRUE + anchored = TRUE + max_integrity = 500 + var/message_char_limit = 300 + var/response_timeout = 2 MINUTES + var/single_cooldown = 5 MINUTES + var/static/list/sender_cooldowns = list() + var/static/list/pending_direct_responses = list() + var/static/response_id_counter = 0 + +/obj/structure/mercstatue/attack_hand(mob/living/carbon/human/user) + use(user) + +/obj/structure/mercstatue/proc/use(mob/living/carbon/human/user) + if(!user) + return + var/obj/item/mercenary_ring/mercring + + if(ishuman(user) && user.mind && istype(user.mind.assigned_role, /datum/job/advclass/mercenary)) + if(tgui_alert(user, "Do you want to change your mercenary description", "MERCENARY", DEFAULT_INPUT_CHOICES, 20 SECONDS) == CHOICE_YES) + user.mercdesc = stripped_input(user, "Write a description which will be shown to potential employers.", "Description", "", message_char_limit) + + if(user in GLOB.contracted_mercenaries) + if(tgui_alert(user, "Do you want to cancel your current contract?", "MERCENARY", DEFAULT_INPUT_CHOICES, 20 SECONDS) == CHOICE_YES) + GLOB.contracted_mercenaries -= user + GLOB.available_mercenaries += user + + if(user in GLOB.available_mercenaries) + if(tgui_alert(user, "Do you want to unregister as an available mercenary for the mercenary statue?", "MERCENARY", DEFAULT_INPUT_CHOICES, 20 SECONDS) == CHOICE_YES) + GLOB.available_mercenaries -= user + for(var/obj/item/mercenary_ring/ring in world) + if(ring.mob_ref) + var/mob/living/M = ring.mob_ref.resolve() + if(M == user) + qdel(ring) + return + if(mercring) + qdel(mercring) + return + + if(ishuman(user) && user.mind && istype(user.mind.assigned_role, /datum/job/advclass/mercenary)) + if(tgui_alert(user, "Do you want to register as an available mercenary for the mercenary statue?", "MERCENARY", DEFAULT_INPUT_CHOICES, 20 SECONDS) == CHOICE_YES) + GLOB.available_mercenaries += user + mercring = new /obj/item/mercenary_ring(src) + mercring.add_mercenary(user) + user.put_in_hands(mercring) + if(user.mercdesc && (user.mercdesc != "")) + return + user.mercdesc = stripped_input(user, "Write a description which will be shown to potential employers.", "Description", "", message_char_limit) + return + + if(length(GLOB.available_mercenaries)) + var/mob/selected_merc = tgui_input_list(user, "Choose an available mercenary", "Mercenaries", GLOB.available_mercenaries) + + if(selected_merc) + var/cooldown_key = "[user.real_name]_[selected_merc.real_name]" + if(sender_cooldowns[cooldown_key]) + var/time_left = sender_cooldowns[cooldown_key] + single_cooldown - world.time + if(time_left > 0) + var/mins_left = max(1, round(time_left / 600)) + to_chat(user, span_warning("I need to wait [mins_left] minute[mins_left == 1 ? "" : "s"] before contacting [selected_merc.real_name] again.")) + return + + if(!Adjacent(user)) + to_chat(user, span_warning("I need to stay close to the statue.")) + return + + var/message = stripped_input(user, "Send a message to [selected_merc] detailing your offer. Maximum [message_char_limit] characters.", "Message for the mercenary", "", message_char_limit) + if(message) + var/response_id = ++response_id_counter + var/response_key = "[response_id]" + sender_cooldowns[cooldown_key] = world.time + pending_direct_responses[response_key] = list("sender" = user, "target" = selected_merc, "message" = message) + addtimer(CALLBACK(src, PROC_REF(expire_direct_response), response_id), response_timeout) + to_chat(selected_merc, span_boldnotice("The mercenary statue whispers in my mind: [message] - [user.real_name]
\[INTERESTED\] | \[NOT INTERESTED\]")) + playsound(selected_merc, 'sound/misc/notice (2).ogg', 100, FALSE, -1) + to_chat(user, "My message has been sent.") + return + + + return + to_chat(user, "No mercenaries are currently available!") + +/obj/structure/mercstatue/Topic(href, href_list) + . = ..() + if(!ishuman(usr)) + return + + var/mob/living/carbon/human/responder = usr + var/response_type = href_list["direct_response"] + var/response_id = href_list["response_id"] + + if(!pending_direct_responses[response_id]) + to_chat(responder, span_warning("That response link has expired or already been used.")) + return + + var/list/response_data = pending_direct_responses[response_id] + var/mob/living/carbon/human/sender = response_data["sender"] + + if(!sender || QDELETED(sender)) + to_chat(responder, span_warning("The sender is no longer available.")) + pending_direct_responses -= response_id + return + + pending_direct_responses -= response_id + if(response_type == "interested") + to_chat(sender, span_notice("[responder.real_name] responded in affirmation to my message.")) + to_chat(responder, span_notice("I responded in affirmation to [sender.real_name].")) + GLOB.available_mercenaries -= responder + GLOB.contracted_mercenaries += responder + else + to_chat(sender, span_notice("[responder.real_name] responded negatively to my message.")) + to_chat(responder, span_notice("I responded negatively to [sender.real_name].")) + + playsound(sender, 'sound/misc/notice (2).ogg', 100, FALSE, -1) + playsound(responder, 'sound/misc/beep.ogg', 100, FALSE, -1) + return + +/obj/structure/mercstatue/proc/expire_direct_response(response_id) + var/response_key = "[response_id]" + if(pending_direct_responses[response_key]) + pending_direct_responses -= response_key + +/obj/structure/mercstatue/examine(mob/user) + . = ..() + + if(GLOB.available_mercenaries) + . += span_notice("These mercenaries are currently available:") + for(var/mob/living/carbon/human/merc in GLOB.available_mercenaries) + if(merc.job) + . += "[merc.real_name], [merc.job]: [merc.mercdesc]" + else + . += "[merc.real_name]: [merc.mercdesc]" + return + else + . += span_notice("No mercenaries are currently available.") diff --git a/code/modules/client/preferences/datums/list/role_preferences/mercenary_description.dm b/code/modules/client/preferences/datums/list/role_preferences/mercenary_description.dm new file mode 100644 index 00000000000..cc99e708fc0 --- /dev/null +++ b/code/modules/client/preferences/datums/list/role_preferences/mercenary_description.dm @@ -0,0 +1,6 @@ +/datum/preference/list_type/role_setting/mercenary_description + savefile_key = "mercenary_description" + setting_display_name = "Mercenary Description" + ui_category = "Mercenary" + example_text = "Professional fistfighter, struck down a troll with my bare hands." + max_lines = 1 diff --git a/code/modules/jobs/job_types/other/mercenary.dm b/code/modules/jobs/job_types/other/mercenary.dm index f661c53e431..dcf067bbe5d 100644 --- a/code/modules/jobs/job_types/other/mercenary.dm +++ b/code/modules/jobs/job_types/other/mercenary.dm @@ -32,7 +32,25 @@ /datum/job/mercenary/after_spawn(mob/living/carbon/human/spawned, client/player_client) . = ..() - to_chat(spawned, "

The Gaffer, who feeds and houses you may have work for you todae, go see him at the office outside your lodgings.

") + if(SSmapping.config?.map_name != "Voyage") + addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/job/mercenary, set_availability), spawned), 0) + if(player_client?.prefs) + var/datum/preferences/prefs = player_client.prefs + spawned.mercdesc = prefs.read_preference(/datum/preference/list_type/role_setting/mercenary_description) + return + +/datum/job/mercenary/proc/set_availability(mob/living/carbon/human/spawned) + if(!spawned || QDELETED(spawned) || !spawned.client) + return + if(tgui_alert(spawned, "Do you want to join the available mercenaries list for the mercenary statue?", "MERCENARY", DEFAULT_INPUT_CHOICES, 30 SECONDS) == CHOICE_YES) + GLOB.available_mercenaries += spawned + to_chat(spawned, "You have been added to the available mercenaries list.") + var/obj/item/mercenary_ring/mercring = new /obj/item/mercenary_ring(spawned.loc) + spawned.put_in_hands(mercring) + mercring.add_mercenary(spawned) + if(spawned.mercdesc && (spawned.mercdesc != "")) + return + spawned.mercdesc = stripped_input(spawned, "Write a description which will be shown to potential employers.", "Description", "", 300) /datum/job/advclass/mercenary department_flag = OUTSIDERS diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 27c0a1d7b21..ccbf3400aca 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -132,6 +132,7 @@ var/breathe_tick = 0 // Used for gas mask delays. var/merctype = 0 // Used for mercenary backgrounds - check mail.dm + var/mercdesc // Description used for mercenary statue. var/tokenclaimed = FALSE // Check for one-time tri reward. possible_rmb_intents = list(/datum/rmb_intent/feint,\ diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index e4fd18d4807..21c18954691 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ diff --git a/icons/roguetown/misc/machines.dmi b/icons/roguetown/misc/machines.dmi index 067ebd2c22b..ff147a93931 100644 Binary files a/icons/roguetown/misc/machines.dmi and b/icons/roguetown/misc/machines.dmi differ diff --git a/vanderlin.dme b/vanderlin.dme index e47d3a623b0..a4bccaaef55 100644 --- a/vanderlin.dme +++ b/vanderlin.dme @@ -2124,6 +2124,7 @@ #include "code\game\objects\items\locks.dm" #include "code\game\objects\items\mageitems.dm" #include "code\game\objects\items\magic.dm" +#include "code\game\objects\items\mercenary_ring.dm" #include "code\game\objects\items\natural.dm" #include "code\game\objects\items\needle.dm" #include "code\game\objects\items\ore.dm" @@ -2287,6 +2288,7 @@ #include "code\game\objects\structures\mannequin.dm" #include "code\game\objects\structures\meathook.dm" #include "code\game\objects\structures\memorial.dm" +#include "code\game\objects\structures\mercstatue.dm" #include "code\game\objects\structures\mirror.dm" #include "code\game\objects\structures\musicbox.dm" #include "code\game\objects\structures\newtree.dm" @@ -2819,6 +2821,7 @@ #include "code\modules\client\preferences\datums\list\role_preferences\_base.dm" #include "code\modules\client\preferences\datums\list\role_preferences\_family_menu.dm" #include "code\modules\client\preferences\datums\list\role_preferences\_ui_menu.dm" +#include "code\modules\client\preferences\datums\list\role_preferences\mercenary_description.dm" #include "code\modules\client\preferences\datums\list\role_preferences\monarch_decree.dm" #include "code\modules\client\preferences\datums\list\role_preferences\monarch_law.dm" #include "code\modules\client\preferences\datums\list\role_preferences\list_pick\_base.dm"