diff --git a/_maps/map_files/dun_world/dun_world.dmm b/_maps/map_files/dun_world/dun_world.dmm
index 16b109c463..bb36d41f4b 100644
--- a/_maps/map_files/dun_world/dun_world.dmm
+++ b/_maps/map_files/dun_world/dun_world.dmm
@@ -17418,13 +17418,7 @@
/turf/open/floor/rogue/cobblerock,
/area/rogue/outdoors/beach)
"eup" = (
-/obj/structure/table/wood/long_table,
-/obj/item/paper,
-/obj/item/paper{
- pixel_x = 4;
- pixel_y = 4
- },
-/obj/item/natural/feather,
+/obj/structure/table/wood/long_table/mid/alt,
/turf/open/floor/carpet/red,
/area/rogue/indoors/town)
"eur" = (
@@ -53070,6 +53064,11 @@
},
/turf/open/floor/carpet/red,
/area/rogue/indoors/inq/basement)
+"nqL" = (
+/obj/structure/chair/wood/rogue/chair3,
+/obj/effect/landmark/start/guild_clerk,
+/turf/open/floor/carpet/red,
+/area/rogue/indoors/town)
"nqN" = (
/obj/machinery/light/rogue/torchholder/lanternpost{
dir = 1
@@ -59822,11 +59821,9 @@
/turf/open/floor/rogue/ruinedwood,
/area/rogue/indoors)
"pfB" = (
-/obj/structure/chair/wood/rogue/chair3,
/obj/structure/fluff/walldeco/sparrowflag{
pixel_y = 32
},
-/obj/effect/landmark/start/mercenary,
/turf/open/floor/carpet/red,
/area/rogue/indoors/town)
"pfG" = (
@@ -61592,6 +61589,7 @@
/obj/effect/decal/wood/herringbone{
dir = 8
},
+/obj/structure/roguemachine/bounty,
/turf/open/floor/rogue/ruinedwood/spiral,
/area/rogue/indoors/town)
"pBu" = (
@@ -92017,6 +92015,18 @@
},
/turf/open/floor/rogue/grass,
/area/rogue/outdoors/rtfield/eora)
+"wWI" = (
+/obj/effect/decal/wood/herringbone{
+ dir = 8
+ },
+/obj/structure/mineral_door/swing_door{
+ keylock = 1;
+ locked = 1;
+ lockid = "merc";
+ name = "Clerk's Office"
+ },
+/turf/open/floor/rogue/ruinedwood/spiral,
+/area/rogue/indoors/town)
"wWM" = (
/obj/structure/table/wood{
icon_state = "tablewood1"
@@ -93710,6 +93720,15 @@
/obj/item/clothing/suit/roguetown/shirt/dress/gen/random,
/turf/open/floor/rogue/blocks/newstone/alt,
/area/rogue/under/cavewet/bogcaves/sunkencity)
+"xsu" = (
+/obj/structure/table/wood/long_table,
+/obj/item/paper{
+ pixel_x = 4;
+ pixel_y = 4
+ },
+/obj/item/natural/feather,
+/turf/open/floor/carpet/red,
+/area/rogue/indoors/town)
"xsx" = (
/obj/item/ash,
/obj/effect/decal/cleanable/dirt/dust,
@@ -278365,9 +278384,9 @@ qJA
qJA
qyR
guo
-guo
pBq
kvk
+wWI
xNs
uky
cBs
@@ -278819,7 +278838,7 @@ qyR
guo
oto
xWk
-xWk
+xsu
dra
dra
dra
@@ -279270,7 +279289,7 @@ pWT
qyR
guo
aSY
-xWk
+nqL
eup
dra
dra
diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm
index 3f384ff2cf..38bfdb7e3d 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -186,6 +186,7 @@
#define MERCENARY (1<<2)
#define DESERT_RIDER (1<<3) //Unused
#define GRENZELHOFT (1<<4) //Unused
+#define MERC_CLERK (1<<5)
#define TRIBAL (1<<8)
@@ -301,6 +302,7 @@
#define JDO_GRENZELHOFT 31.1
#define JDO_DESERT_RIDER 31.2
#define JDO_VET 31.4
+#define JDO_MERC_CLERK 31.5
#define JDO_VAGRANT 33
#define JDO_ORPHAN 34
@@ -388,6 +390,7 @@
#define WANDERER_ROLES \
/datum/job/roguetown/pilgrim,\
/datum/job/roguetown/adventurer,\
+ /datum/job/roguetown/guild_clerk,\
/datum/job/roguetown/mercenary/desert_rider,\
/datum/job/roguetown/mercenary/grenzelhoft,\
/datum/job/roguetown/bandit,\
diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm
index 3dddee7df0..eaa16f7f48 100644
--- a/code/game/objects/effects/landmarks.dm
+++ b/code/game/objects/effects/landmarks.dm
@@ -334,6 +334,10 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
name = "Mercenary"
icon_state = "arrow"
+/obj/effect/landmark/start/guild_clerk
+ name = "Guild Clerk"
+ icon_state = "arrow"
+
/obj/effect/landmark/start/vagrant
name = "Beggar"
icon_state = "arrow"
diff --git a/code/modules/jobs/job_types/roguetown/mercenaries/guild_clerk.dm b/code/modules/jobs/job_types/roguetown/mercenaries/guild_clerk.dm
new file mode 100644
index 0000000000..b481ff5a4e
--- /dev/null
+++ b/code/modules/jobs/job_types/roguetown/mercenaries/guild_clerk.dm
@@ -0,0 +1,57 @@
+/datum/job/roguetown/guild_clerk
+ title = "Guild Clerk"
+ flag = MERC_CLERK
+ department_flag = MERCENARIES
+ tutorial = "Tidy. Crass. Foul mouthed. The first words that come to mind for the unsung hero of the mercenary guild's contract clerk. \
+ The medium by which the mercenaries go through for the average town goer to secure a mercenary and a fair price to accompany. \
+ Not only are you responsible for organizing contracts for the mercenaries, but also ensuring the proper use and delivery of the rewards from the excidium. \
+ It's a thankless task, but someone has to do it. May as well be you."
+ allowed_sexes = list(MALE, FEMALE)
+ allowed_races = RACES_FEARED_UP // ES equivalent of upstream's RACES_VERY_SHUNNED_UP
+ allowed_ages = ALL_AGES_LIST
+ outfit = /datum/outfit/job/roguetown/mercenary/guild_clerk
+ display_order = JDO_MERC_CLERK
+ selection_color = JCOLOR_MERCENARY
+ faction = "Station"
+ total_positions = 1
+ spawn_positions = 1
+ min_pq = 6
+ max_pq = null
+ job_reopens_slots_on_death = TRUE
+ always_show_on_latechoices = TRUE
+
+/datum/outfit/job/roguetown/mercenary/guild_clerk/pre_equip(mob/living/carbon/human/H)
+ ..()
+ ADD_TRAIT(H, TRAIT_SEEPRICES, type)
+
+ //More-or-less just combat clerk. DO YOUR JOB!!!!!!!
+ if(H.mind)
+ H.adjust_skillrank(/datum/skill/combat/wrestling, 3, TRUE)
+ H.adjust_skillrank(/datum/skill/combat/unarmed, 3, TRUE)
+ H.adjust_skillrank(/datum/skill/combat/maces, 3, TRUE)
+ H.adjust_skillrank(/datum/skill/misc/reading, 5, TRUE)
+ H.adjust_skillrank(/datum/skill/misc/athletics, 3, TRUE)
+ H.adjust_skillrank(/datum/skill/misc/swimming, 2, TRUE)
+ H.adjust_skillrank(/datum/skill/misc/climbing, 2, TRUE)
+ H.adjust_skillrank(/datum/skill/craft/cooking, 2, TRUE)
+ H.adjust_skillrank(/datum/skill/misc/medicine, 1, TRUE)
+ H.change_stat("strength", 1)
+ H.change_stat("intelligence", 2)
+ H.change_stat("fortune", 1)
+
+ //Clerk stuff.
+ if(H.gender == MALE)
+ armor = /obj/item/clothing/cloak/tabard/knight
+ shirt = /obj/item/clothing/suit/roguetown/shirt/undershirt
+ pants = /obj/item/clothing/under/roguetown/tights
+
+ if(H.gender == FEMALE)
+ shirt = /obj/item/clothing/suit/roguetown/shirt/dress/noble // upstream uses /nobledress/green which doesn't exist in ES
+ pants = /obj/item/clothing/under/roguetown/tights/black // upstream uses /stockings/white which doesn't exist in ES
+
+ shoes = /obj/item/clothing/shoes/roguetown/boots/leather // upstream uses /armor/leather; ES uses /boots/leather
+ belt = /obj/item/storage/belt/rogue/leather
+ beltr = /obj/item/storage/belt/rogue/pouch/coins/rich //To pay out contracts, if the issuer flakes.
+ backl = /obj/item/rogueweapon/mace
+ beltl = /obj/item/roguekey/mercenary // Regular merc key — ES doesn't have upstream's /storage/keyring/mercenary_boss "captain office" keyring.
+ backr = /obj/item/storage/backpack/rogue/satchel
diff --git a/code/modules/jobs/jobs.dm b/code/modules/jobs/jobs.dm
index 2b16564ba7..0b32d308ae 100644
--- a/code/modules/jobs/jobs.dm
+++ b/code/modules/jobs/jobs.dm
@@ -148,6 +148,7 @@ GLOBAL_LIST_INIT(mercenary_positions, list(
"Grenzelhoft Mercenary",
"Desert Rider Mercenary",
"Veteran",
+ "Guild Clerk",
))
GLOBAL_LIST_INIT(youngfolk_positions, list(
diff --git a/code/modules/roguetown/roguemachine/bounty.dm b/code/modules/roguetown/roguemachine/bounty.dm
index ec506cd757..f5000874ee 100644
--- a/code/modules/roguetown/roguemachine/bounty.dm
+++ b/code/modules/roguetown/roguemachine/bounty.dm
@@ -22,11 +22,16 @@
var/target_height
var/target_voice
var/target_voice_prefix
+ /// 5-digit identifier used by the Guild Clerk to withdraw rewards on non-lethal/task bounties.
+ var/number
/// Whats displayed when consulting the bounties
var/banner
///Is this a bandit bounty?
var/bandit
+ /// "Yes" if this bounty's reward can be paid out manually via the Guild Clerk withdraw menu.
+ /// Set automatically to "Yes" for custom (no-target) task bounties.
+ var/withdrawable
/obj/structure/roguemachine/bounty/attack_hand(mob/user)
@@ -37,6 +42,8 @@
// Main Menu
var/list/choices = list("Consult Bounties", "Set Bounty", "Print List of Bounties", "Remove Bounty", "Collect Change")
+ if(user.mind?.assigned_role == "Guild Clerk")
+ choices += "Withdraw Bounty Reward"
var/selection = input(user, "The Excidium listens", src) as null|anything in choices
switch(selection)
@@ -57,6 +64,9 @@
budget2change(budget)
budget = 0
+ if("Withdraw Bounty Reward")
+ withdraw_bounty(H)
+
/obj/structure/roguemachine/bounty/attackby(obj/item/P, mob/user, params)
if(!(ishuman(user))) return
@@ -115,32 +125,56 @@
///Sets a bounty on a target player through user input.
///@param user: The player setting the bounty.
/obj/structure/roguemachine/bounty/proc/set_bounty(mob/living/carbon/human/user)
- var/list/eligible_players = list()
-
- if(user.mind.known_people.len)
- for(var/mob/living/carbon/human/H in GLOB.human_list)
- if(H.real_name in user.mind.known_people)
- eligible_players[H.real_name] = H
- else
- to_chat(user, span_warning("I don't know anyone."))
- return
-
- var/choice = input(user, "Whose name shall be etched on the wanted list?", src) as null|anything in eligible_players
- if(isnull(choice))
- say("No target selected.")
+ // Up-front choice between lethal (kill target, head delivery) and non-lethal (task bounty).
+ var/mode = input(user, "What kind of bounty do you want set?", src) as null|anything in list("Lethal", "Non-lethal")
+ if(isnull(mode))
return
- var/mob/living/carbon/human/target = eligible_players[choice]
+ var/mob/living/carbon/human/target
+ var/reason
+ var/withdrawable
+
+ if(mode == "Lethal")
+ // Lethal bounty: must name a known target. No withdraw — payout only via head delivery.
+ var/list/eligible_players = list()
+ if(user.mind.known_people.len)
+ for(var/mob/living/carbon/human/H in GLOB.human_list)
+ if(H.real_name in user.mind.known_people)
+ eligible_players[H.real_name] = H
+ if(!length(eligible_players))
+ to_chat(user, span_warning("I don't know anyone."))
+ return
+ var/choice = input(user, "Whose name shall be etched on the wanted list?", src) as null|anything in eligible_players
+ if(isnull(choice))
+ say("No target selected.")
+ return
+ target = eligible_players[choice]
+ withdrawable = "No"
+ reason = input(user, "For what sins do you summon the hounds of hell?", src) as null|text
+ if(isnull(reason) || reason == "")
+ say("No reason given.")
+ return
+ else
+ // Non-lethal bounty: custom task description, withdrawable by Guild Clerk via bounty number.
+ withdrawable = "Yes"
+ reason = input(user, "What work do you desire to be done?", src) as null|text
+ if(isnull(reason) || reason == "")
+ say("No task given.")
+ return
- var/amount = input(user, "How many mammons shall be stained red for their demise?", src) as null|num
+ var/amount = input(user, "How many mammons shall be rewarded for this task to be carried out?", src) as null|num
if(isnull(amount))
say("Invalid amount.")
return
- if(amount < 100)
- say("Insufficient amount. Bounty must be at least 100 mammon.")
- return
- if(amount > 500)
- say("Insufficient amount. Bounties cannot be more than 500 mammon.")
+ if(target)
+ if(amount < 100)
+ say("Insufficient amount. Bounty must be at least 100 mammon.")
+ return
+ if(amount > 500)
+ say("Insufficient amount. Bounties cannot be more than 500 mammon.")
+ return
+ else if(amount < 20)
+ say("Insufficient amount. Task bounty must be at least 20 mammon.")
return
// Has user enough money?
@@ -148,49 +182,48 @@
say("Insufficient funds.")
return
- var/reason = input(user, "For what sins do you summon the hounds of hell?", src) as null|text
- if(isnull(reason) || reason == "")
- say("No reason given.")
- return
-
- var/confirm = input(user, "Do you dare unleash this darkness upon the world? Your name will be known.", src) as null|anything in list("Yes", "No")
+ var/confirm = input(user, "Do you dare unleash this task upon the world? Your name will be known.", src) as null|anything in list("Yes", "No")
if(isnull(confirm) || confirm == "No") return
+ var/number = rand(10000, 99999)
+
// Deduct money from user
budget -= round(amount)
- //Deduct royal tax from amount
- var/royal_tax = round(amount * 0.1)
+ //Deduct royal tax from amount — pulls from the kingdom's current tax setting.
+ var/royal_tax = round(amount * SStreasury.tax_value)
SStreasury.treasury_value += royal_tax
SStreasury.log_entries += "+[royal_tax] to treasury (bounty tax)"
amount -= royal_tax
- var/race = target.dna.species
- var/gender = target.gender
- var/list/d_list = target.get_mob_descriptors()
- var/descriptor_height = build_coalesce_description_nofluff(d_list, target, list(MOB_DESCRIPTOR_SLOT_HEIGHT), "%DESC1%")
- var/descriptor_body = build_coalesce_description_nofluff(d_list, target, list(MOB_DESCRIPTOR_SLOT_BODY), "%DESC1%")
- var/descriptor_voice = build_coalesce_description_nofluff(d_list, target, list(MOB_DESCRIPTOR_SLOT_VOICE), "%DESC1%")
-
- // Finally create bounty
- add_bounty(target.real_name, race, gender, descriptor_height, descriptor_body, descriptor_voice, amount, FALSE, reason, user.real_name)
+ // Finally create bounty. Null target means a custom task bounty (withdrawable by Guild Clerk via bounty number).
+ add_bounty(target?.real_name, amount, FALSE, reason, user.real_name, withdrawable, number)
//Announce it locally and on scomm
playsound(src, 'sound/misc/machinetalk.ogg', 100, FALSE, -1)
- var/bounty_announcement = "The Excidium hungers for [target]."
+ var/bounty_announcement
+ if(target)
+ bounty_announcement = "The Excidium hungers for [target]."
+ else
+ bounty_announcement = "The Excidium offers [amount] mammon for a new deed."
say(bounty_announcement)
scom_announce(bounty_announcement)
- message_admins("[ADMIN_LOOKUPFLW(user)] has set a bounty on [ADMIN_LOOKUPFLW(target)] with the reason of: '[reason]'")
+ if(target)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has set a bounty on [ADMIN_LOOKUPFLW(target)] with the reason of: '[reason]'")
+ else
+ message_admins("[ADMIN_LOOKUPFLW(user)] has set a task bounty (number [number]) with the reason of: '[reason]'")
-/proc/add_bounty(target_realname, amount, bandit_status, reason, employer_name)
+/proc/add_bounty(target_realname, amount, bandit_status, reason, employer_name, withdrawable, number)
var/datum/bounty/new_bounty = new /datum/bounty
new_bounty.amount = amount
new_bounty.target = target_realname
new_bounty.bandit = bandit_status
new_bounty.reason = reason
new_bounty.employer = employer_name
+ new_bounty.withdrawable = withdrawable
+ new_bounty.number = number
compose_bounty(new_bounty)
GLOB.head_bounties += new_bounty
@@ -229,18 +262,54 @@
///Composes a random bounty banner based on the given bounty info.
///@param new_bounty: The bounty datum.
/proc/compose_bounty(datum/bounty/new_bounty)
- switch(rand(1, 3))
- if(1)
- new_bounty.banner += "A dire bounty hangs upon the capture of [new_bounty.target], for '[new_bounty.reason]'.
"
- new_bounty.banner += "The patron, [new_bounty.employer], offers [new_bounty.amount] mammons for the task.
"
- if(2)
- new_bounty.banner += "The capture of [new_bounty.target] is wanted for '[new_bounty.reason]''.
"
- new_bounty.banner += "The employer, [new_bounty.employer], offers [new_bounty.amount] mammons for the deed.
"
- if(3)
- new_bounty.banner += "[new_bounty.employer] hath offered to pay [new_bounty.amount] mammons for the capture of [new_bounty.target].
"
- new_bounty.banner += "By reason of the following: '[new_bounty.reason]'.
"
+ if(new_bounty.target)
+ switch(rand(1, 3))
+ if(1)
+ new_bounty.banner += "A dire bounty hangs upon the capture of [new_bounty.target], for '[new_bounty.reason]'.
"
+ new_bounty.banner += "The patron, [new_bounty.employer], offers [new_bounty.amount] mammons for the task.
"
+ if(2)
+ new_bounty.banner += "The capture of [new_bounty.target] is wanted for '[new_bounty.reason]''.
"
+ new_bounty.banner += "The employer, [new_bounty.employer], offers [new_bounty.amount] mammons for the deed.
"
+ if(3)
+ new_bounty.banner += "[new_bounty.employer] hath offered to pay [new_bounty.amount] mammons for the capture of [new_bounty.target].
"
+ new_bounty.banner += "By reason of the following: '[new_bounty.reason]'.
"
+ if(new_bounty.withdrawable == "Yes")
+ new_bounty.banner += "This bounty is able to be completed without the beheading of [new_bounty.target], talk to an authorized member of the mercenaries guild for the reward.
"
+ new_bounty.banner += "BOUNTY NUMBER: [new_bounty.number].
"
+ else
+ switch(rand(1, 3))
+ if(1)
+ new_bounty.banner += "[new_bounty.employer] hath offered to pay [new_bounty.amount] mammons to carry out a task.
"
+ new_bounty.banner += "Said task is to: '[new_bounty.reason]'.
"
+ if(2)
+ new_bounty.banner += "The employer [new_bounty.employer] is offering [new_bounty.amount] mammons to accomplish a certain deed.
"
+ new_bounty.banner += "The deed is the following: '[new_bounty.reason]'.
"
+ if(3)
+ new_bounty.banner += "[new_bounty.amount] mammons are being offered to whoever can '[new_bounty.reason]'.
"
+ new_bounty.banner += "The patron, [new_bounty.employer] will pay the first to complete it.
"
+ new_bounty.banner += "The reward for this task shall be given when verified by an authorized member of the mercenaries guild.
"
+ new_bounty.banner += "BOUNTY NUMBER: [new_bounty.number].
"
new_bounty.banner += "--------------
"
+/// Guild-Clerk-only menu option. Pays out a withdrawable bounty's reward by bounty number.
+/obj/structure/roguemachine/bounty/proc/withdraw_bounty(mob/user)
+ var/bounty_number = input(user, "What is the number of the bounty whose reward is to be withdrawn?", src) as null|num
+ if(isnull(bounty_number))
+ say("No number given.")
+ return
+ for(var/datum/bounty/b in GLOB.head_bounties)
+ if(b.number == bounty_number && b.withdrawable == "Yes")
+ var/confirm = input(user, "Are you sure you would like to withdraw the reward [b.target ? "on [b.target]'s head" : "of this task"] and mark it as completed? There will be punishment from the guild for any reported improper use.", src) as null|anything in list("Yes", "No")
+ if(isnull(confirm) || confirm == "No")
+ return
+ budget2change(b.amount, user)
+ GLOB.head_bounties -= b
+ say("Bounty successfully marked as completed and reward withdrawn.")
+ scom_announce("Bounty number [bounty_number] has been marked as completed.")
+ message_admins("[ADMIN_LOOKUPFLW(user)] withdrew bounty number [bounty_number].")
+ return
+ say("There are no withdrawable bounties with that number. Please confirm that the bounty is withdrawable, and deliver head for cranial inspection otherwise.")
+
/proc/compose_bounty_noface(datum/bounty/new_bounty_noface)
switch(rand(1, 3))
if(1)
diff --git a/roguetown.dme b/roguetown.dme
index 3728297a3d..f5bee72eba 100644
--- a/roguetown.dme
+++ b/roguetown.dme
@@ -1712,6 +1712,7 @@
#include "code\modules\jobs\job_types\roguetown\Inquisition\orthoclasses\confessor.dm"
#include "code\modules\jobs\job_types\roguetown\Inquisition\orthoclasses\disciple.dm"
#include "code\modules\jobs\job_types\roguetown\Inquisition\orthoclasses\psydoniantemplar.dm"
+#include "code\modules\jobs\job_types\roguetown\mercenaries\guild_clerk.dm"
#include "code\modules\jobs\job_types\roguetown\mercenaries\mercenary.dm"
#include "code\modules\jobs\job_types\roguetown\mercenaries\classes\_mercenary.dm"
#include "code\modules\jobs\job_types\roguetown\mercenaries\classes\atgervi.dm"