diff --git a/baystation12.dme b/baystation12.dme
index d3fe8271b4e..2eae08c0638 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -282,6 +282,7 @@
#include "code\game\gamemodes\wizard\wizard.dm"
#include "code\game\jobs\access.dm"
#include "code\game\jobs\job_controller.dm"
+#include "code\game\jobs\job_objective.dm"
#include "code\game\jobs\jobprocs.dm"
#include "code\game\jobs\jobs.dm"
#include "code\game\jobs\whitelist.dm"
@@ -295,6 +296,7 @@
#include "code\game\jobs\job\science.dm"
#include "code\game\jobs\job\security.dm"
#include "code\game\jobs\job\silicon.dm"
+#include "code\game\jobs\job_objectives\science.dm"
#include "code\game\machinery\adv_med.dm"
#include "code\game\machinery\ai_slipper.dm"
#include "code\game\machinery\airlock_control.dm"
diff --git a/code/datums/helper_datums/construction_datum.dm b/code/datums/helper_datums/construction_datum.dm
index 7c2fabbc9b8..03c0eba193a 100644
--- a/code/datums/helper_datums/construction_datum.dm
+++ b/code/datums/helper_datums/construction_datum.dm
@@ -57,8 +57,18 @@
return 0
- proc/spawn_result()
+ proc/spawn_result(mob/user as mob)
if(result)
+ // PAY ME
+ var/taskpath=null
+ switch(result)
+ if(/obj/mecha/working/ripley)
+ taskpath = /datum/job_objective/make_ripley
+ if(taskpath)
+ var/datum/job_objective/task = user.mind.findJobTask(taskpath)
+ if(istype(task))
+ task.unit_completed()
+
new result(get_turf(holder))
spawn()
del holder
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index de62826dffa..ed05177a4a0 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -99,6 +99,15 @@ datum/mind
output += "Objective #[obj_count]: [objective.explanation_text]"
obj_count++
+ if(job_objectives.len>0)
+ output += "
Job Objectives:"
+
+ var/obj_count = 1
+ for(var/datum/job_objective/objective in job_objectives)
+ output += "- Task #[obj_count]: [objective.get_description()]
"
+ obj_count++
+ output += "
"
+
recipient << browse(output,"window=memory")
proc/edit_memory()
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 2bb9526117d..33a9ef20d6b 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -22,7 +22,7 @@
var/explosion_in_progress = 0 //sit back and relax
var/list/datum/mind/modePlayer = new
var/list/restricted_jobs = list() // Jobs it doesn't make sense to be. I.E chaplain or AI cultist
- var/list/protected_jobs = list() // Jobs that can't be tratiors because
+ var/list/protected_jobs = list() // Jobs that can't be trators
var/required_players = 0
var/required_players_secret = 0 //Minimum number of players for that game mode to be chose in Secret
var/required_enemies = 0
@@ -70,10 +70,6 @@ Implants;
(Pointless) Badassery;
/obj/item/toy/syndicateballoon:10:For showing that You Are The BOSS (Useless Balloon);"}
-// Items removed from above:
-/*
-/obj/item/weapon/cloaking_device:4:Cloaking Device; //Replacing cloakers with thermals. -Pete
-*/
/datum/game_mode/proc/announce() //to be calles when round starts
world << "Notice: [src] did not define announce()"
@@ -122,6 +118,70 @@ Implants;
/datum/game_mode/proc/process()
return 0
+//Called by the gameticker
+/datum/game_mode/proc/process_job_tasks()
+ var/obj/machinery/message_server/useMS = null
+ if(message_servers)
+ for (var/obj/machinery/message_server/MS in message_servers)
+ if(MS.active)
+ useMS = MS
+ break
+ for(var/mob/M in player_list)
+ if(M.mind)
+ var/obj/item/device/pda/P=null
+ for(var/obj/item/device/pda/check_pda in sortAtom(PDAs))
+ if (check_pda.owner==M.name)
+ P=check_pda
+ break
+ var/count=0
+ for(var/datum/job_objective/objective in M.mind.job_objectives)
+ count++
+ var/msg=""
+ var/pay=0
+ if(objective.per_unit && objective.units_needing_compensation>0)
+ var/newunits = objective.units_needing_compensation
+ msg="We see that you completed [newunits] new unit[newunits>1?"s":""] for Task #[count]! "
+ pay=objective.completion_payment * newunits
+ else if(!objective.completed)
+ if(objective.is_completed())
+ pay=objective.completion_payment
+ msg="Task #[count] completed! "
+ if(pay>0)
+ if(M.mind.initial_account)
+ M.mind.initial_account.money += objective.completion_payment
+ var/datum/transaction/T = new()
+ T.target_name = "[command_name()] Payroll"
+ T.purpose = "Payment"
+ T.amount = objective.completion_payment
+ T.date = current_date_string
+ T.time = worldtime2text()
+ T.source_terminal = "\[CLASSIFIED\] Terminal #[rand(111,333)]"
+ M.mind.initial_account.transaction_log.Add(T)
+ msg += "You have been sent the $[pay], as agreed."
+ else
+ msg += "However, we were unable to send you the $[pay] you're entitled."
+ if(useMS)
+ // THIS SHOULD HAVE DONE EVERYTHING FOR ME
+ useMS.send_pda_message("[P.owner]", "[command_name()] Payroll", msg)
+
+ // BUT NOPE, NEED TO DO THIS BULLSHIT.
+ P.tnote += "← From [command_name()] (Payroll):
[msg]
"
+
+ if (!P.silent)
+ playsound(P.loc, 'sound/machines/twobeep.ogg', 50, 1)
+ for (var/mob/O in hearers(3, P.loc))
+ if(!P.silent) O.show_message(text("\icon[P] *[P.ttone]*"))
+ //Search for holder of the PDA.
+ var/mob/living/L = null
+ if(P.loc && isliving(P.loc))
+ L = P.loc
+ //Maybe they are a pAI!
+ else
+ L = get(P, /mob/living/silicon)
+
+ if(L)
+ L << "\icon[P] Message from [command_name()] (Payroll), \"[msg]\" (Unable to Reply)"
+ break
/datum/game_mode/proc/check_finished() //to be called by ticker
if(emergency_shuttle.location==2 || station_was_nuked)
diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm
index 4d83aa5eefa..edf58d23756 100644
--- a/code/game/gamemodes/gameticker.dm
+++ b/code/game/gamemodes/gameticker.dm
@@ -298,6 +298,7 @@ var/global/datum/controller/gameticker/ticker
return 0
mode.process()
+ mode.process_job_tasks()
emergency_shuttle.process()
@@ -366,6 +367,8 @@ var/global/datum/controller/gameticker/ticker
mode.declare_completion()//To declare normal completion.
+ mode.declare_job_completion()
+
//calls auto_declare_completion_* for all modes
for(var/handler in typesof(/datum/game_mode/proc))
if (findtext("[handler]","auto_declare_completion_"))
diff --git a/code/game/jobs/job/job.dm b/code/game/jobs/job/job.dm
index 682c80f1741..2c312a49ff5 100644
--- a/code/game/jobs/job/job.dm
+++ b/code/game/jobs/job/job.dm
@@ -41,6 +41,12 @@
//If you have use_age_restriction_for_jobs config option enabled and the database set up, this option will add a requirement for players to be at least minimal_player_age days old. (meaning they first signed in at least that many days before.)
var/minimal_player_age = 0
+ /////////////////////////////////
+ // /vg/ feature: Job Objectives!
+ /////////////////////////////////
+ var/required_objectives=list() // Objectives that are ALWAYS added.
+ var/optional_objectives=list() // Objectives that are SOMETIMES added.
+
/datum/job/proc/equip(var/mob/living/carbon/human/H)
return 1
diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm
index 8f0f10c214d..059423206b2 100644
--- a/code/game/jobs/job/science.dm
+++ b/code/game/jobs/job/science.dm
@@ -19,6 +19,11 @@
access_RC_announce, access_keycard_auth, access_tcomsat, access_gateway, access_xenoarch)
minimal_player_age = 7
+ // All science-y guys get bonuses for maxing out their tech.
+ required_objectives=list(
+ /datum/job_objective/maximize_research
+ )
+
equip(var/mob/living/carbon/human/H)
if(!H) return 0
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/heads/rd(H), slot_ears)
@@ -49,6 +54,11 @@
minimal_access = list(access_tox, access_tox_storage, access_research, access_xenoarch)
alt_titles = list("Xenoarcheologist", "Anomalist", "Plasma Researcher")
+ // All science-y guys get bonuses for maxing out their tech.
+ required_objectives=list(
+ /datum/job_objective/maximize_research
+ )
+
equip(var/mob/living/carbon/human/H)
if(!H) return 0
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_sci(H), slot_ears)
@@ -76,6 +86,12 @@
access = list(access_robotics, access_tox, access_tox_storage, access_research, access_xenobiology)
minimal_access = list(access_research, access_xenobiology)
+ // All science-y guys get bonuses for maxing out their tech.
+ required_objectives=list(
+ /datum/job_objective/maximize_research
+ )
+
+
equip(var/mob/living/carbon/human/H)
if(!H) return 0
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_sci(H), slot_ears)
@@ -103,6 +119,12 @@
minimal_access = list(access_robotics, access_tech_storage, access_morgue, access_research) //As a job that handles so many corpses, it makes sense for them to have morgue access.
alt_titles = list("Biomechanical Engineer","Mechatronic Engineer")
+ required_objectives=list(
+ /datum/job_objective/make_cyborg,
+// /datum/job_objective/make_mommi,
+ /datum/job_objective/make_ripley
+ )
+
equip(var/mob/living/carbon/human/H)
if(!H) return 0
H.equip_to_slot_or_del(new /obj/item/device/radio/headset/headset_sci(H), slot_ears)
diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm
index fc5f7df203a..7ede79586b2 100644
--- a/code/game/jobs/job_controller.dm
+++ b/code/game/jobs/job_controller.dm
@@ -59,6 +59,17 @@ var/global/datum/controller/occupations/job_master
Debug("Player: [player] is now Rank: [rank], JCP:[job.current_positions], JPL:[position_limit]")
player.mind.assigned_role = rank
player.mind.role_alt_title = GetPlayerAltTitle(player, rank)
+
+ // JOB OBJECTIVES OH SHIT
+ player.mind.job_objectives.Cut()
+ for(var/objectiveType in job.required_objectives)
+ new objectiveType(player.mind)
+
+ // 50/50 chance of getting optional objectives.
+ for(var/objectiveType in job.optional_objectives)
+ if(prob(50))
+ new objectiveType(player.mind)
+
unassigned -= player
job.current_positions++
return 1
diff --git a/code/game/jobs/job_objective.dm b/code/game/jobs/job_objective.dm
new file mode 100644
index 00000000000..1c8bbe79fcc
--- /dev/null
+++ b/code/game/jobs/job_objective.dm
@@ -0,0 +1,83 @@
+/datum/mind/var/list/job_objectives=list()
+
+#define FINDJOBTASK_DEFAULT_NEW 1 // Make a new task of this type if one can't be found.
+/datum/mind/proc/findJobTask(var/typepath,var/options)
+ var/datum/job_objective/task = locate(typepath) in src.job_objectives
+ if(!istype(task,typepath))
+ if(options & FINDJOBTASK_DEFAULT_NEW)
+ task = new typepath()
+ src.job_objectives += task
+ return task
+ else
+ return null
+
+/datum/job_objective
+ var/datum/mind/owner = null //Who owns the objective.
+ var/completed = 0 //currently only used for custom objectives.
+ var/per_unit = 0
+ var/units_completed = 0
+ var/units_needing_compensation = 0 // Shit not yet paid for
+ var/units_requested = INFINITY
+ var/completion_payment = 0 // Credits paid to owner when completed
+
+/datum/job_objective/New(var/datum/mind/new_owner)
+ owner=new_owner
+ owner.job_objectives += src
+
+/datum/job_objective/Del()
+
+/datum/job_objective/proc/get_description()
+ return "Placeholder objective."
+
+/datum/job_objective/proc/unit_completed(var/count=1)
+ units_completed += count
+ units_needing_compensation += count
+
+/datum/job_objective/proc/is_completed()
+ if(!completed)
+ completed = check_for_completion()
+ return completed
+
+/datum/job_objective/proc/check_for_completion()
+ return 0
+
+/datum/game_mode/proc/declare_job_completion()
+ var/text = "Job Completion:"
+ for(var/datum/mind/employee in player_list)
+ if(!employee.job_objectives.len)//If the employee had no objectives, don't need to process this.
+ return
+ var/tasks_completed=0
+
+ //text += "[L.name] ([ckey(D.mind.key)]), the [L.job]:\n"
+ text += "
[employee.key] was [employee.name] ("
+ if(employee.current)
+ if(employee.current.stat == DEAD)
+ text += "died"
+ else
+ text += "survived"
+ if(employee.current.real_name != employee.name)
+ text += " as [employee.current.real_name]"
+ else
+ text += "body destroyed"
+ text += ")"
+
+ var/count = 1
+ for(var/datum/job_objective/objective in employee.job_objectives)
+ if(objective.is_completed(1))
+ text += "
Task #[count]: [objective.get_description()] Completed!"
+ feedback_add_details("employee_objective","[objective.type]|SUCCESS")
+ tasks_completed++
+ else
+ text += "
Task #[count]: [objective.get_description()] Fail."
+ feedback_add_details("employee_objective","[objective.type]|FAIL")
+ count++
+
+ if(tasks_completed>=1)
+ text += "
The [employee.assigned_role] did their fucking job!"
+ feedback_add_details("employee_success","SUCCESS")
+ else
+ text += "
The [employee.assigned_role] was a worthless sack of shit!"
+ feedback_add_details("employee_success","FAIL")
+
+ world << text
+ return 1
\ No newline at end of file
diff --git a/code/game/jobs/job_objectives/science.dm b/code/game/jobs/job_objectives/science.dm
new file mode 100644
index 00000000000..996193ba66e
--- /dev/null
+++ b/code/game/jobs/job_objectives/science.dm
@@ -0,0 +1,60 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Research
+/////////////////////////////////////////////////////////////////////////////////////////
+
+// MAXIMUM SCIENCE
+/datum/job_objective/maximize_research
+ completion_payment=1000
+
+/datum/job_objective/maximize_research/get_description()
+ return "Maximize all legal research tech levels."
+
+/datum/job_objective/maximize_research/check_for_completion()
+ var/obj/machinery/r_n_d/server/server = null
+ for(var/obj/machinery/r_n_d/server/serber in machines)
+ if(serber.name == "Core R&D Server")
+ server=serber
+ break
+ if(!server)
+ // This was just used for testing.
+ //world << "UNABLE TO FIND A GODDAMN RND SERVER. FUCK."
+ return
+ for(var/datum/tech/T in server.files.possible_tech)
+ if(T.max_level==0) // Ignore illegal tech, etc
+ continue
+ var/datum/tech/KT = locate(T.type, server.files.known_tech)
+ if(!KT)
+ return 0 // Obviously haven't maxed everything if we don't know a tech.
+ if(KT.levelDestructive Analyzer Menu
"
if(linked_lathe != null) dat += "Protolathe Construction Menu
"
if(linked_imprinter != null) dat += "Circuit Construction Menu
"
+ if(user.client.holder) dat += "MAXIMUM SCIENCE
"
dat += "Settings"
if(1.1) //Research viewer
diff --git a/code/modules/research/research.dm b/code/modules/research/research.dm
index 40b534bf0eb..b0b89f4ed25 100644
--- a/code/modules/research/research.dm
+++ b/code/modules/research/research.dm
@@ -173,6 +173,7 @@ datum/tech //Datum of individual technologies.
var/desc = "description" //General description of what it does and what it makes.
var/id = "id" //An easily referenced ID. Must be alphanumeric, lower-case, and no symbols.
var/level = 1 //A simple number scale of the research level. Level 0 = Secret tech.
+ var/max_level = 1 // Maximum level this can be at (for job objectives)
var/list/req_tech = list() //List of ids associated values of techs required to research this tech. "id" = #
@@ -182,51 +183,61 @@ datum/tech/materials
name = "Materials Research"
desc = "Development of new and improved materials."
id = "materials"
+ max_level=6
datum/tech/engineering
name = "Engineering Research"
desc = "Development of new and improved engineering parts and."
id = "engineering"
+ max_level=5
datum/tech/plasmatech
name = "Plasma Research"
desc = "Research into the mysterious substance colloqually known as 'plasma'."
id = "plasmatech"
+ max_level=3
datum/tech/powerstorage
name = "Power Manipulation Technology"
desc = "The various technologies behind the storage and generation of electicity."
id = "powerstorage"
+ max_level=6
datum/tech/bluespace
name = "'Blue-space' Research"
desc = "Research into the sub-reality known as 'blue-space'"
id = "bluespace"
+ max_level=10
datum/tech/biotech
name = "Biological Technology"
desc = "Research into the deeper mysteries of life and organic substances."
id = "biotech"
+ max_level=7
datum/tech/combat
name = "Combat Systems Research"
desc = "The development of offensive and defensive systems."
id = "combat"
+ max_level=6
datum/tech/magnets
name = "Electromagnetic Spectrum Research"
desc = "Research into the electromagnetic spectrum. No clue how they actually work, though."
id = "magnets"
+ max_level=5
datum/tech/programming
name = "Data Theory Research"
desc = "The development of new computer and artificial intelligence and data storage systems."
id = "programming"
+ max_level=4
datum/tech/syndicate
name = "Illegal Technologies Research"
desc = "The study of technologies that violate standard Nanotrasen regulations."
id = "syndicate"
+ max_level=0 // Don't count towards maxed research, since it's illegal.
/*
datum/tech/arcane