Files
Leland Kemble 0991484c87 Patrol bounties pay money (#96251)
## About The Pull Request

Because patrols do not send anything material via the pad, they never
register `contribution` naturally, and thus runtime when their cube is
created.

Makes patrol bounties register ID `contribution` when they begin
tracking an ID's movements.

## Why It's Good For The Game

I did thousands of credits of unpaid labor before i realized these
weren't working

## Changelog
🆑

fix: Patrol bounties properly pay those who complete them

/🆑
2026-05-29 18:00:02 -04:00

230 lines
7.6 KiB
Plaintext

///Bounties that require you to perform documentation and inspection of your department to send to centcom.
/datum/bounty/patrol
name = "Patrol Station"
description = "Perform a routine patrol of %AREA_NAME%. \
You must travel at least %AREA_COVERAGE% meters within the area. \
Your ID card will update you as you progress."
reward = CARGO_CRATE_VALUE * 5
allow_duplicate = TRUE
global_exempt = TRUE
/// Ref to the component applied to the ID card to track movement.
VAR_PRIVATE/datum/tracker
/// Typepath of what area we want patrolled.
VAR_FINAL/area/demanded_area
/// How much area coverage is needed to complete the patrol.
VAR_FINAL/needed_coverage = -1
/// List of turf IDs that have been walked on during the patrol.
VAR_FINAL/list/walked_turfs
/// Number 0-5 tracking which progress alerts have been sent.
VAR_FINAL/alerted = 0
/datum/bounty/patrol/New()
demanded_area = pick(get_patrol_area_types() & GLOB.areas_by_type)
var/total_coverage = 0
for(var/turf/open/floor/walkable in GLOB.areas_by_type[demanded_area].get_turfs_from_all_zlevels())
total_coverage += 1
needed_coverage = round(total_coverage * rand(4, 8) * 0.1, 1)
name += ": [initial(demanded_area.name)]"
description = replacetext(description, "%AREA_NAME%", initial(demanded_area.name))
description = replacetext(description, "%AREA_COVERAGE%", needed_coverage)
// scale the reward based on how big the area is, so you don't feel like you're wasting time
// central primary hallway can have somewhere in the ballpark of 500 turfs
// but something like the bar only sits around 100-200
reward *= (needed_coverage / 100)
/datum/bounty/patrol/can_get()
// only give out bounties worth completing.
return needed_coverage >= 20
/datum/bounty/patrol/proc/get_patrol_area_types()
return typecacheof(list(
/area/station/commons,
/area/station/hallway,
/area/station/maintenance,
/area/station/security/checkpoint,
/area/station/security/prison,
/area/station/service,
))
/datum/bounty/patrol/proc/get_progress()
var/progress = 0
for(var/turf_id, count in walked_turfs)
progress += count
return progress
/datum/bounty/patrol/print_required()
return "[get_progress()]/[needed_coverage] meters"
/datum/bounty/patrol/can_claim()
return get_progress() >= needed_coverage
/datum/bounty/patrol/on_selected(obj/item/card/id/id_card)
start_tracking(id_card)
/datum/bounty/patrol/proc/start_tracking(obj/item/card/id/id_card)
contribution |= id_card.registered_account
tracker = AddComponent(/datum/component/connect_containers, id_card, list(COMSIG_MOVABLE_MOVED = PROC_REF(on_card_moved)))
RegisterSignal(id_card, COMSIG_MOVABLE_MOVED, PROC_REF(on_card_moved))
/datum/bounty/patrol/on_reset(obj/item/card/id/id_card)
stop_tracking(id_card)
/datum/bounty/patrol/proc/stop_tracking(obj/item/card/id/id_card)
QDEL_NULL(tracker)
UnregisterSignal(id_card, COMSIG_MOVABLE_MOVED)
/datum/bounty/patrol/proc/on_card_moved(atom/movable/moving, atom/old_loc, ...)
SIGNAL_HANDLER
var/turf/new_turf = get_turf(moving)
if(!isfloorturf(new_turf) || new_turf == get_turf(old_loc) || new_turf.loc.type != demanded_area)
return
var/turf_id = "[new_turf.x],[new_turf.y],[new_turf.z]"
// you can retread the same tiles, but only up to three times.
// prevents cheese (walking between the same two tiles over and over),
// but still allows smaller rooms to be completable without being frustrating.
if(LAZYACCESS(walked_turfs, turf_id) >= 3)
return
var/obj/item/card/id/id_card
if(isliving(moving))
var/mob/living/living_mover = moving
id_card = living_mover.get_idcard()
else if(isidcard(moving))
id_card = moving
if(isnull(id_card))
id_card = locate() in moving.get_all_contents()
if(id_card?.registered_account?.civilian_bounty != src)
return
LAZYADDASSOC(walked_turfs, turf_id, 1)
var/progress = get_progress()
if(alerted == 0)
alerted = 1
id_card.registered_account.bank_card_talk("Patrol started. \
Travel [needed_coverage] meters in the area to complete your patrol.", force = TRUE)
return
var/progress_percent = progress / needed_coverage
if(progress_percent >= 0.25 && alerted < 2)
alerted = 2
id_card.registered_account.bank_card_talk("Patrol 25% complete.", force = TRUE)
return
if(progress_percent >= 0.5 && alerted < 3)
alerted = 3
id_card.registered_account.bank_card_talk("Patrol 50% complete.", force = TRUE)
return
if(progress_percent >= 0.75 && alerted < 4)
alerted = 4
id_card.registered_account.bank_card_talk("Patrol 75% complete.", force = TRUE)
return
if(progress >= needed_coverage && alerted < 5)
alerted = 5
id_card.registered_account.bank_card_talk("Patrol complete. \
Return to the bounty terminal to claim your reward.", force = TRUE)
stop_tracking(id_card) // don't need this anymore
return
/datum/bounty/patrol/supply
name = "Patrol Cargo"
allow_duplicate = FALSE
/datum/bounty/patrol/supply/get_patrol_area_types()
return typecacheof(list(
/area/station/cargo/breakroom,
/area/station/cargo/lobby,
/area/station/cargo/lower,
/area/station/cargo/mining_breakroom,
/area/station/cargo/miningdock,
/area/station/cargo/miningfoundry,
/area/station/cargo/miningoffice,
/area/station/cargo/office,
/area/station/cargo/sorting,
/area/station/cargo/storage,
/area/station/cargo/warehouse,
/area/station/maintenance/department/cargo,
)) + list(
/area/station/cargo, // base cargo type as well
/area/mine/lounge, // and public mining on lavaland
)
/datum/bounty/patrol/medical
name = "Patrol Medical"
allow_duplicate = FALSE
/datum/bounty/patrol/medical/get_patrol_area_types()
return typecacheof(list(
/area/station/maintenance/department/medical,
/area/station/medical/abandoned,
/area/station/medical/break_room,
/area/station/medical/cryo,
/area/station/medical/exam_room,
/area/station/medical/lower,
/area/station/medical/medbay,
/area/station/medical/morgue,
/area/station/medical/office,
/area/station/medical/storage,
/area/station/medical/surgery,
/area/station/medical/treatment_center,
)) + list(
/area/station/medical, // base medical type as well
)
/datum/bounty/patrol/science
name = "Patrol Science"
allow_duplicate = FALSE
/datum/bounty/patrol/science/get_patrol_area_types()
return typecacheof(list(
/area/station/science/auxlab,
/area/station/science/breakroom,
/area/station/science/explab,
/area/station/science/lab,
/area/station/science/lobby,
/area/station/science/lower,
/area/station/science/research,
/area/station/science/research/abandoned,
)) + list(
/area/station/science, // base science type as well
/area/station/maintenance/department/science, // and only base sci maint - not xenobio maint
)
/datum/bounty/patrol/engineering
name = "Patrol Engineering"
allow_duplicate = FALSE
/datum/bounty/patrol/engineering/get_patrol_area_types()
return typecacheof(list(
/area/station/engineering/break_room,
/area/station/engineering/lobby,
/area/station/engineering/main,
/area/station/engineering/storage,
/area/station/engineering/storage_shared,
/area/station/maintenance/department/engine,
/area/station/maintenance/department/electrical,
)) + list(
/area/station/engineering, // plus base engineering
/area/station/engineering/atmos, // and base atmospherics
)
/datum/bounty/item/contraband
name = "Confiscated Contraband"
description = "The Syndicate is constantly acting to subvert crewmates of Nanotrasen-affiliated stations. Ship us your latest batch of confiscated contraband."
reward = CARGO_CRATE_VALUE * 4
required_count = 5
wanted_types = list(/obj/item = TRUE)
/datum/bounty/item/contraband/applies_to(obj/O)
return HAS_TRAIT(O, TRAIT_CONTRABAND)