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"