Merge remote-tracking branch 'Meghan-Rossi/master' into job_description_alt

This commit is contained in:
Meghan-Rossi
2020-03-04 19:07:37 +00:00
230 changed files with 3301 additions and 1129 deletions

View File

@@ -88,7 +88,7 @@
return 1
/obj/machinery/atmospherics/omni/atmos_filter/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
usr.set_machine(src)
user.set_machine(src)
var/list/data = new()

View File

@@ -3,26 +3,38 @@
//Picks from the list, with some safeties, and returns the "default" arg if it fails
#define DEFAULTPICK(L, default) ((istype(L, /list) && L:len) ? pick(L) : default)
// Ensures L is initailized after this point
#define LAZYINITLIST(L) if (!L) L = list()
// Sets a L back to null iff it is empty
#define UNSETEMPTY(L) if (L && !length(L)) L = null
// Removes I from list L, and sets I to null if it is now empty
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
// Adds I to L, initalizing I if necessary
// Adds I to L, initalizing L if necessary
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
#define LAZYFIND(L, V) L ? L.Find(V) : 0
// Reads I from L safely - Works with both associative and traditional lists.
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
// Turns LAZYINITLIST(L) L[K] = V into ... for associated lists
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
// Reads the length of L, returning 0 if null
#define LAZYLEN(L) length(L)
// Null-safe L.Cut()
#define LAZYCLEARLIST(L) if(L) L.Cut()
// Reads L or an empty list if L is not a list. Note: Does NOT assign, L may be an expression.
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
// binary search sorted insert

View File

@@ -130,6 +130,8 @@ What is the naming convention for planes or layers?
#define PLANE_ADMIN2 33 //Purely for shenanigans (above lighting)
#define PLANE_BUILDMODE 39 //Things that only show up when you have buildmode on
//Fullscreen overlays under inventory
#define PLANE_FULLSCREEN 90 //Blindness, mesons, druggy, etc
#define OBFUSCATION_LAYER 5 //Where images covering the view for eyes are put

View File

@@ -0,0 +1,11 @@
///Protects a datum from being VV'd
#define GENERAL_PROTECT_DATUM(Path)\
##Path/can_vv_get(var_name){\
return FALSE;\
}\
##Path/vv_edit_var(var_name, var_value){\
return FALSE;\
}\
##Path/CanProcCall(procname){\
return FALSE;\
}

View File

@@ -113,6 +113,7 @@
#define MAX_RECORD_LENGTH 24576
#define MAX_LNAME_LEN 64
#define MAX_NAME_LEN 52
#define MAX_FEEDBACK_LENGTH 4096
#define MAX_TEXTFILE_LENGTH 128000 // 512GQ file
// Event defines.
@@ -236,17 +237,20 @@
#define ANTAG_SHARED "Shared"
#define ANTAG_KNOWN "Known"
// Job groups
#define ROLE_COMMAND "command"
#define ROLE_SECURITY "security"
#define ROLE_ENGINEERING "engineering"
#define ROLE_MEDICAL "medical"
#define ROLE_RESEARCH "research"
#define ROLE_CARGO "cargo"
#define ROLE_CIVILIAN "civilian"
#define ROLE_SYNTHETIC "synthetic"
#define ROLE_UNKNOWN "unknown"
#define ROLE_EVERYONE "everyone"
// Departments.
#define DEPARTMENT_COMMAND "Command"
#define DEPARTMENT_SECURITY "Security"
#define DEPARTMENT_ENGINEERING "Engineering"
#define DEPARTMENT_MEDICAL "Medical"
#define DEPARTMENT_RESEARCH "Research"
#define DEPARTMENT_CARGO "Cargo"
#define DEPARTMENT_CIVILIAN "Civilian"
#define DEPARTMENT_PLANET "Planetside" // I hate having this be here and not in a SC file. Hopefully someday the manifest can be rewritten to be map-agnostic.
#define DEPARTMENT_SYNTHETIC "Synthetic"
// These are mostly for the department guessing code and event system.
#define DEPARTMENT_UNKNOWN "Unknown"
#define DEPARTMENT_EVERYONE "Everyone"
// Canonical spellings of TSCs, so typos never have to happen again due to human error.
#define TSC_NT "NanoTrasen"

View File

@@ -394,7 +394,9 @@
#define VIS_OBJS 20
#define VIS_MOBS 21
#define VIS_COUNT 21 //Must be highest number from above.
#define VIS_BUILDMODE 22
#define VIS_COUNT 22 //Must be highest number from above.
//Some mob icon layering defines
#define BODY_LAYER -100

View File

@@ -0,0 +1,7 @@
#define SQLITE_TABLE_FEEDBACK "feedback"
#define SQLITE_FEEDBACK_COLUMN_ID "id"
#define SQLITE_FEEDBACK_COLUMN_AUTHOR "author"
#define SQLITE_FEEDBACK_COLUMN_TOPIC "topic"
#define SQLITE_FEEDBACK_COLUMN_CONTENT "content"
#define SQLITE_FEEDBACK_COLUMN_DATETIME "datetime"

View File

@@ -52,6 +52,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
// Subsystem init_order, from highest priority to lowest priority
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_SQLITE 19
#define INIT_ORDER_CHEMISTRY 18
#define INIT_ORDER_MAPPING 17
#define INIT_ORDER_DECALS 16
@@ -68,6 +69,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_XENOARCH -20
#define INIT_ORDER_CIRCUIT -21
#define INIT_ORDER_AI -22
#define INIT_ORDER_JOB -23
// Subsystem fire priority, from lowest to highest priority

View File

@@ -48,7 +48,7 @@ var/datum/category_collection/underwear/global_underwear = new()
//Backpacks
var/global/list/backbaglist = list("Nothing", "Backpack", "Satchel", "Satchel Alt", "Messenger Bag")
var/global/list/pdachoicelist = list("Default", "Slim", "Old", "Rugged")
var/global/list/pdachoicelist = list("Default", "Slim", "Old", "Rugged", "Holographic")
var/global/list/exclude_jobs = list(/datum/job/ai,/datum/job/cyborg)
// Visual nets

View File

@@ -34,12 +34,23 @@
// Sorts jobs by department, and then by flag within department
/proc/cmp_job_datums(var/datum/job/a, var/datum/job/b)
. = sorttext(b.department, a.department)
. = 0
if( LAZYLEN(a.departments) && LAZYLEN(b.departments) )
var/list/common_departments = a.departments & b.departments // Makes a list that contains only departments that were in both.
if(!common_departments.len)
. = sorttext(b.departments[1], a.departments[1])
if(. == 0) //Same department, push up if they're a head
. = b.head_position - a.head_position
if (. == 0) //Already in head/nothead spot, sort by name
. = b.sorting_order - a.sorting_order
if(. == 0) //Already in same sorting order, sort by name
. = sorttext(b.title, a.title)
/proc/cmp_department_datums(var/datum/department/a, var/datum/department/b)
. = b.sorting_order - a.sorting_order // First, sort by the sorting order vars.
if(. == 0) // If they have the same var, then sort by name.
. = sorttext(b.name, a.name)
// Sorts entries in a performance stats list.
/proc/cmp_generic_stat_item_time(list/A, list/B)
. = B[STAT_ENTRY_TIME] - A[STAT_ENTRY_TIME]

View File

@@ -1571,3 +1571,11 @@ var/mob/dview/dview_mob = new
else
return "\[[url_encode(thing.tag)]\]"
return "\ref[input]"
// Painlessly creates an <a href=...> element.
// First argument is where to send the Topic call to when clicked. Should be a reference to an object. This is generally src, but not always.
// Second one is for all the params that will be sent. Uses an assoc list (e.g. "value" = "5").
// Note that object refs will be converted to text, as if \ref[thing] was done. To get the ref back on Topic() side, you will need to use locate().
// Third one is the text that will be clickable.
/proc/href(href_src, list/href_params, href_text)
return "<a href='?src=\ref[href_src];[list2params(href_params)]'>[href_text]</a>"

View File

@@ -103,7 +103,7 @@
var/sdepth = A.storage_depth(src)
if((!isturf(A) && A == loc) || (sdepth != -1 && sdepth <= 1))
if(W)
var/resolved = W.resolve_attackby(A, src, params)
var/resolved = W.resolve_attackby(A, src, click_parameters = params)
if(!resolved && A && W)
W.afterattack(A, src, 1, params) // 1 indicates adjacency
else
@@ -124,7 +124,7 @@
if(A.Adjacent(src) || (W && W.attack_can_reach(src, A, W.reach)) ) // see adjacent.dm
if(W)
// Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example)
var/resolved = W.resolve_attackby(A,src, params)
var/resolved = W.resolve_attackby(A,src, click_parameters = params)
if(!resolved && A && W)
W.afterattack(A, src, 1, params) // 1: clicking something Adjacent
else

View File

@@ -37,11 +37,11 @@ avoid code duplication. This includes items that may sometimes act as a standard
/atom/proc/attackby(obj/item/W, mob/user, var/attack_modifier, var/click_parameters)
return
/atom/movable/attackby(obj/item/W, mob/user, var/attack_modifier)
/atom/movable/attackby(obj/item/W, mob/user, var/attack_modifier, var/click_parameters)
if(!(W.flags & NOBLUDGEON))
visible_message("<span class='danger'>[src] has been hit by [user] with [W].</span>")
/mob/living/attackby(obj/item/I, mob/user, var/attack_modifier)
/mob/living/attackby(obj/item/I, mob/user, var/attack_modifier, var/click_parameters)
if(!ismob(user))
return 0
if(can_operate(src) && I.do_surgery(src,user))

View File

@@ -252,6 +252,17 @@ var/list/gamemode_cache = list()
var/random_submap_orientation = FALSE // If true, submaps loaded automatically can be rotated.
var/autostart_solars = FALSE // If true, specifically mapped in solar control computers will set themselves up when the round starts.
// New shiny SQLite stuff.
// The basics.
var/sqlite_enabled = FALSE // If it should even be active. SQLite can be ran alongside other databases but you should not have them do the same functions.
// In-Game Feedback.
var/sqlite_feedback = FALSE // Feedback cannot be submitted if this is false.
var/list/sqlite_feedback_topics = list("General") // A list of 'topics' that feedback can be catagorized under by the submitter.
var/sqlite_feedback_privacy = FALSE // If true, feedback submitted can have its author name be obfuscated. This is not 100% foolproof (it's md5 ffs) but can stop casual snooping.
var/sqlite_feedback_cooldown = 0 // How long one must wait, in days, to submit another feedback form. Used to help prevent spam, especially with privacy active. 0 = No limit.
var/sqlite_feedback_min_age = 0 // Used to block new people from giving feedback. This metric is very bad but it can help slow down spammers.
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
for (var/T in L)
@@ -841,6 +852,23 @@ var/list/gamemode_cache = list()
if("autostart_solars")
config.autostart_solars = TRUE
if("sqlite_enabled")
config.sqlite_enabled = TRUE
if("sqlite_feedback")
config.sqlite_feedback = TRUE
if("sqlite_feedback_topics")
config.sqlite_feedback_topics = splittext(value, ";")
if(!config.sqlite_feedback_topics.len)
config.sqlite_feedback_topics += "General"
if("sqlite_feedback_privacy")
config.sqlite_feedback_privacy = TRUE
if("sqlite_feedback_cooldown")
config.sqlite_feedback_cooldown = text2num(value)
else

View File

@@ -0,0 +1,108 @@
SUBSYSTEM_DEF(job)
name = "Job"
init_order = INIT_ORDER_JOB
flags = SS_NO_FIRE
var/list/occupations = list() //List of all jobs
var/list/datum/job/name_occupations = list() //Dict of all jobs, keys are titles
var/list/type_occupations = list() //Dict of all jobs, keys are types
var/list/department_datums = list()
var/debug_messages = FALSE
/datum/controller/subsystem/job/Initialize(timeofday)
if(!department_datums.len)
setup_departments()
if(!occupations.len)
setup_occupations()
return ..()
/datum/controller/subsystem/job/proc/setup_occupations(faction = "Station")
occupations = list()
var/list/all_jobs = subtypesof(/datum/job)
if(!all_jobs.len)
to_chat(world, span("warning", "Error setting up jobs, no job datums found"))
return FALSE
for(var/J in all_jobs)
var/datum/job/job = new J()
if(!job)
continue
if(job.faction != faction)
continue
occupations += job
name_occupations[job.title] = job
type_occupations[J] = job
if(LAZYLEN(job.departments))
add_to_departments(job)
sortTim(occupations, /proc/cmp_job_datums)
for(var/D in department_datums)
var/datum/department/dept = department_datums[D]
sortTim(dept.jobs, /proc/cmp_job_datums, TRUE)
return TRUE
/datum/controller/subsystem/job/proc/add_to_departments(datum/job/J)
for(var/D in J.departments)
var/datum/department/dept = LAZYACCESS(department_datums, D)
if(!istype(dept))
job_debug_message("Job '[J.title]' is defined as being inside department '[D]', but it does not exist.")
continue
dept.jobs[J.title] = J
/datum/controller/subsystem/job/proc/setup_departments()
for(var/t in subtypesof(/datum/department))
var/datum/department/D = new t()
department_datums[D.name] = D
sortTim(department_datums, /proc/cmp_department_datums, TRUE)
/datum/controller/subsystem/job/proc/get_all_department_datums()
var/list/dept_datums = list()
for(var/D in department_datums)
dept_datums += department_datums[D]
return dept_datums
/datum/controller/subsystem/job/proc/get_job(rank)
if(!occupations.len)
setup_occupations()
return name_occupations[rank]
/datum/controller/subsystem/job/proc/get_job_type(jobtype)
if(!occupations.len)
setup_occupations()
return type_occupations[jobtype]
// Determines if a job title is inside of a specific department.
// Useful to replace the old `if(job_title in command_positions)` code.
/datum/controller/subsystem/job/proc/is_job_in_department(rank, target_department_name)
var/datum/department/D = LAZYACCESS(department_datums, target_department_name)
if(istype(D))
return LAZYFIND(D.jobs, rank) ? TRUE : FALSE
return FALSE
// Returns a list of all job names in a specific department.
/datum/controller/subsystem/job/proc/get_job_titles_in_department(target_department_name)
var/datum/department/D = LAZYACCESS(department_datums, target_department_name)
if(istype(D))
var/list/job_titles = list()
for(var/J in D.jobs)
job_titles += J
return job_titles
job_debug_message("Was asked to get job titles for a non-existant department '[target_department_name]'.")
return list()
// Someday it might be good to port code/game/jobs/job_controller.dm to here and clean it up.
/datum/controller/subsystem/job/proc/job_debug_message(message)
if(debug_messages)
log_debug("JOB DEBUG: [message]")

View File

@@ -24,7 +24,7 @@ SUBSYSTEM_DEF(planets)
..()
/datum/controller/subsystem/planets/proc/createPlanets()
var/list/planet_datums = subtypesof(/datum/planet)
var/list/planet_datums = using_map.planet_datums_to_make
for(var/P in planet_datums)
var/datum/planet/NP = new P()
planets.Add(NP)

View File

@@ -0,0 +1,187 @@
// This holds all the code needed to manage and use a SQLite database.
// It is merely a file sitting inside the data directory, as opposed to a full fledged DB service,
// however this makes it a lot easier to test, and it is natively supported by BYOND.
SUBSYSTEM_DEF(sqlite)
name = "SQLite"
init_order = INIT_ORDER_SQLITE
flags = SS_NO_FIRE
var/database/sqlite_db = null
/datum/controller/subsystem/sqlite/Initialize(timeofday)
connect()
if(sqlite_db)
init_schema(sqlite_db)
return ..()
/datum/controller/subsystem/sqlite/proc/connect()
if(!config.sqlite_enabled)
return
if(!sqlite_db)
sqlite_db = new("data/sqlite/sqlite.db") // The path has to be hardcoded or BYOND silently fails.
if(!sqlite_db)
to_world_log("Failed to load or create a SQLite database.")
log_debug("ERROR: SQLite database is active in config but failed to load.")
else
to_world_log("Sqlite database connected.")
// Makes the tables, if they do not already exist in the sqlite file.
/datum/controller/subsystem/sqlite/proc/init_schema(database/sqlite_object)
// Feedback table.
// Note that this is for direct feedback from players using the in-game feedback system and NOT for stat tracking.
// Player ckeys are not stored in this table as a unique key due to a config option to hash the keys to encourage more honest feedback.
/*
* id - Primary unique key to ID a specific piece of feedback.
NOT used to id people submitting feedback.
* author - The person who submitted it. Will be the ckey, or a hash of the ckey,
if both the config supports it, and the user wants it.
* topic - A specific category to organize feedback under. Options are defined in the config file.
* content - What the author decided to write to the staff. Limited to MAX_FEEDBACK_LENGTH.
* datetime - When the author submitted their feedback, acts as a timestamp.
*/
var/database/query/init_schema = new(
{"
CREATE TABLE IF NOT EXISTS [SQLITE_TABLE_FEEDBACK]
(
`[SQLITE_FEEDBACK_COLUMN_ID]` INTEGER NOT NULL UNIQUE,
`[SQLITE_FEEDBACK_COLUMN_AUTHOR]` TEXT NOT NULL,
`[SQLITE_FEEDBACK_COLUMN_TOPIC]` TEXT NOT NULL,
`[SQLITE_FEEDBACK_COLUMN_CONTENT]` TEXT NOT NULL,
`[SQLITE_FEEDBACK_COLUMN_DATETIME]` TEXT NOT NULL,
PRIMARY KEY(`[SQLITE_FEEDBACK_COLUMN_ID]`)
);
"}
)
init_schema.Execute(sqlite_object)
sqlite_check_for_errors(init_schema, "Feedback table creation")
// Add more schemas below this if the SQLite DB gets expanded for things like persistant news, polls, bans, deaths, etc.
// General error checking for SQLite.
// Returns true if something went wrong. Also writes a log.
// The desc parameter should be unique for each call, to make it easier to track down where the error occured.
/datum/controller/subsystem/sqlite/proc/sqlite_check_for_errors(var/database/query/query_used, var/desc)
if(query_used && query_used.ErrorMsg())
log_debug("SQLite Error: [desc] : [query_used.ErrorMsg()]")
return TRUE
return FALSE
/************
* Feedback *
************/
// Inserts data into the feedback table in a painless manner.
// Returns TRUE if no issues happened, FALSE otherwise.
/datum/controller/subsystem/sqlite/proc/insert_feedback(author, topic, content, database/sqlite_object)
if(!author || !topic || !content)
CRASH("One or more parameters was invalid.")
// Sanitize everything to avoid sneaky stuff.
var/sqlite_author = sql_sanitize_text(ckey(lowertext(author)))
var/sqlite_content = sql_sanitize_text(content)
var/sqlite_topic = sql_sanitize_text(topic)
var/database/query/query = new(
"INSERT INTO [SQLITE_TABLE_FEEDBACK] (\
[SQLITE_FEEDBACK_COLUMN_AUTHOR], \
[SQLITE_FEEDBACK_COLUMN_TOPIC], \
[SQLITE_FEEDBACK_COLUMN_CONTENT], \
[SQLITE_FEEDBACK_COLUMN_DATETIME]) \
\
VALUES (\
?,\
?,\
?,\
datetime('now'))",
sqlite_author,
sqlite_topic,
sqlite_content
)
query.Execute(sqlite_object)
return !sqlite_check_for_errors(query, "Insert Feedback")
/datum/controller/subsystem/sqlite/proc/can_submit_feedback(client/C)
if(!config.sqlite_enabled)
return FALSE
if(config.sqlite_feedback_min_age && !is_old_enough(C))
return FALSE
if(config.sqlite_feedback_cooldown > 0 && get_feedback_cooldown(C.key, config.sqlite_feedback_cooldown, sqlite_db) > 0)
return FALSE
return TRUE
// Returns TRUE if the player is 'old' enough, according to the config.
/datum/controller/subsystem/sqlite/proc/is_old_enough(client/C)
if(get_player_age(C.key) < config.sqlite_feedback_min_age)
return FALSE
return TRUE
// Returns how many days someone has to wait, to submit more feedback, or 0 if they can do so right now.
/datum/controller/subsystem/sqlite/proc/get_feedback_cooldown(player_ckey, cooldown, database/sqlite_object)
player_ckey = sql_sanitize_text(ckey(lowertext(player_ckey)))
var/potential_hashed_ckey = sql_sanitize_text(md5(player_ckey + SSsqlite.get_feedback_pepper()))
// First query is to get the most recent time the player has submitted feedback.
var/database/query/query = new({"
SELECT [SQLITE_FEEDBACK_COLUMN_DATETIME]
FROM [SQLITE_TABLE_FEEDBACK]
WHERE [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ? OR [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ?
ORDER BY [SQLITE_FEEDBACK_COLUMN_DATETIME]
DESC LIMIT 1;
"},
player_ckey,
potential_hashed_ckey
)
query.Execute(sqlite_object)
sqlite_check_for_errors(query, "Rate Limited Check 1")
// It is possible this is their first time, so there won't be a next row.
if(query.NextRow()) // If this is true, the user has submitted feedback at least once.
var/list/row_data = query.GetRowData()
var/last_submission_datetime = row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]
// Now we have the datetime, we need to do something to compare it.
// Second query is to calculate the difference between two datetimes.
// This is done on the SQLite side because parsing datetimes with BYOND is probably a bad idea.
query = new(
"SELECT julianday('now') - julianday(?) \
AS 'datediff';",
last_submission_datetime
)
query.Execute(sqlite_object)
sqlite_check_for_errors(query, "Rate Limited Check 2")
query.NextRow()
row_data = query.GetRowData()
var/date_diff = row_data["datediff"]
// Now check if it's too soon to give more feedback.
if(text2num(date_diff) < cooldown) // Too soon.
return round(cooldown - date_diff, 0.1)
return 0.0
// A Pepper is like a Salt but only one exists and is supposed to be outside of a database.
// If the file is properly protected, it can only be viewed/copied by sys-admins generating a log, which is much more conspicious than accessing/copying a DB.
// This stops mods/admins/etc from guessing the author by shoving names in an MD5 hasher until they pick the right one.
// Don't use this for things needing actual security.
/datum/controller/subsystem/sqlite/proc/get_feedback_pepper()
var/pepper_file = file2list("config/sqlite_feedback_pepper.txt")
var/pepper = null
for(var/line in pepper_file)
if(!line)
continue
if(length(line) == 0)
continue
else if(copytext(line, 1, 2) == "#")
continue
else
pepper = line
break
return pepper
/datum/controller/subsystem/sqlite/CanProcCall(procname)
return procname != "get_feedback_pepper"

View File

@@ -54,25 +54,25 @@
//to_world("[name]: [rank]")
//cael - to prevent multiple appearances of a player/job combination, add a continue after each line
var/department = 0
if(real_rank in command_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_COMMAND))
heads[name] = rank
department = 1
if(real_rank in security_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_SECURITY))
sec[name] = rank
department = 1
if(real_rank in engineering_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_ENGINEERING))
eng[name] = rank
department = 1
if(real_rank in medical_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_MEDICAL))
med[name] = rank
department = 1
if(real_rank in science_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_RESEARCH))
sci[name] = rank
department = 1
if(real_rank in cargo_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_CARGO))
car[name] = rank
department = 1
if(real_rank in civilian_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_CIVILIAN))
civ[name] = rank
department = 1
if(!department && !(name in heads))

View File

@@ -0,0 +1,52 @@
GLOBAL_VAR(managed_browser_id_ticker)
// This holds information on managing a /datum/browser object.
// Managing can include things like persisting the state of specific information inside of this object, receiving Topic() calls, or deleting itself when the window is closed.
// This is useful for browser windows to be able to stand 'on their own' instead of being tied to something in the game world, like an object or mob.
/datum/managed_browser
var/client/my_client = null
var/browser_id = null
var/base_browser_id = null
var/title = null
var/size_x = 200
var/size_y = 400
var/display_when_created = TRUE
/datum/managed_browser/New(client/new_client)
if(!new_client)
crash_with("Managed browser object was not given a client.")
return
if(!base_browser_id)
crash_with("Managed browser object does not have a base browser id defined in its type.")
return
my_client = new_client
browser_id = "[base_browser_id]-[GLOB.managed_browser_id_ticker++]"
if(display_when_created)
display()
/datum/managed_browser/Destroy()
my_client = null
return ..()
// Override if you want to have the browser title change conditionally.
// Otherwise it's easier to just change the title variable directly.
/datum/managed_browser/proc/get_title()
return title
// Override to display the html information.
// It is suggested to build it with a list, and use list.Join() at the end.
// This helps prevent excessive concatination, which helps preserves BYOND's string tree from becoming a laggy mess.
/datum/managed_browser/proc/get_html()
return
/datum/managed_browser/proc/display()
interact(get_html(), get_title(), my_client)
/datum/managed_browser/proc/interact(html, title, client/C)
var/datum/browser/popup = new(C.mob, browser_id, title, size_x, size_y, src)
popup.set_content(html)
popup.open()

View File

@@ -0,0 +1,147 @@
/client
var/datum/managed_browser/feedback_form/feedback_form = null
/client/can_vv_get(var_name)
return var_name != NAMEOF(src, feedback_form) // No snooping.
GENERAL_PROTECT_DATUM(datum/managed_browser/feedback_form)
// A fairly simple object to hold information about a player's feedback as it's being written.
// Having this be it's own object instead of being baked into /mob/new_player allows for it to be used
// from other places than just the lobby, and makes it a lot harder for people with dev powers to be naughty with it using VV/proccall.
/datum/managed_browser/feedback_form
base_browser_id = "feedback_form"
title = "Server Feedback"
size_x = 480
size_y = 520
var/feedback_topic = null
var/feedback_body = null
var/feedback_hide_author = FALSE
/datum/managed_browser/feedback_form/New(client/new_client)
feedback_topic = config.sqlite_feedback_topics[1]
..(new_client)
/datum/managed_browser/feedback_form/Destroy()
if(my_client)
my_client.feedback_form = null
return ..()
// Privacy option is allowed if both the config allows it, and the pepper file exists and isn't blank.
/datum/managed_browser/feedback_form/proc/can_be_private()
return config.sqlite_feedback_privacy && SSsqlite.get_feedback_pepper()
/datum/managed_browser/feedback_form/display()
if(!my_client)
return
if(!SSsqlite.can_submit_feedback(my_client))
return
..()
// Builds the window for players to review their feedback.
/datum/managed_browser/feedback_form/get_html()
var/list/dat = list("<html><body>")
dat += "<center>"
dat += "<font size='2'>"
dat += "Here, you can write some feedback for the server.<br>"
dat += "Note that HTML is NOT supported!<br>"
dat += "Click the edit button to begin writing.<br>"
dat += "Your feedback is currently [length(feedback_body)]/[MAX_FEEDBACK_LENGTH] letters long."
dat += "</font>"
dat += "<hr>"
dat += "<h2>Preview</h2></center>"
dat += "Author: "
if(can_be_private())
if(!feedback_hide_author)
dat += "[my_client.ckey] "
dat += span("linkOn", "<b>Visible</b>")
dat += " | "
dat += href(src, list("feedback_hide_author" = 1), "Hashed")
else
dat += "[md5(ckey(lowertext(my_client.ckey + SSsqlite.get_feedback_pepper())))] "
dat += href(src, list("feedback_hide_author" = 0), "Visible")
dat += " | "
dat += span("linkOn", "<b>Hashed</b>")
else
dat += my_client.ckey
dat += "<br>"
if(config.sqlite_feedback_topics.len > 1)
dat += "Topic: [href(src, list("feedback_choose_topic" = 1), feedback_topic)]<br>"
else
dat += "Topic: [config.sqlite_feedback_topics[1]]<br>"
dat += "<br>"
if(feedback_body)
dat += replacetext(feedback_body, "\n", "<br>") // So newlines will look like they work in the preview.
else
dat += "<i>\[Feedback goes here...\]</i>"
dat += "<br>"
dat += href(src, list("feedback_edit_body" = 1), "Edit")
dat += "<hr>"
if(config.sqlite_feedback_cooldown)
dat += "<i>Please note that you will have to wait [config.sqlite_feedback_cooldown] day\s before \
being able to write more feedback after submitting.</i><br>"
dat += href(src, list("feedback_submit" = 1), "Submit")
dat += "</body></html>"
return dat.Join()
/datum/managed_browser/feedback_form/Topic(href, href_list[])
if(!my_client)
return FALSE
if(href_list["feedback_edit_body"])
// This is deliberately not sanitized here, and is instead checked when hitting the submission button,
// as we want to give the user a chance to fix it without needing to rewrite the whole thing.
feedback_body = input(my_client, "Please write your feedback here.", "Feedback Body", feedback_body) as null|message
display() // Refresh the window with new information.
return
if(href_list["feedback_hide_author"])
if(!can_be_private())
feedback_hide_author = FALSE
else
feedback_hide_author = text2num(href_list["feedback_hide_author"])
display()
return
if(href_list["feedback_choose_topic"])
feedback_topic = input(my_client, "Choose the topic you want to submit your feedback under.", "Feedback Topic", feedback_topic) in config.sqlite_feedback_topics
display()
return
if(href_list["feedback_submit"])
// Do some last minute validation, and tell the user if something goes wrong,
// so we don't wipe out their ten thousand page essay due to having a few too many characters.
if(length(feedback_body) > MAX_FEEDBACK_LENGTH)
to_chat(my_client, span("warning", "Your feedback is too long, at [length(feedback_body)] characters, where as the \
limit is [MAX_FEEDBACK_LENGTH]. Please shorten it and try again."))
return
var/text = sanitize(feedback_body, max_length = 0, encode = TRUE, trim = FALSE, extra = FALSE)
if(!text) // No text, or it was super invalid.
to_chat(my_client, span("warning", "It appears you didn't write anything, or it was invalid."))
return
if(alert(my_client, "Are you sure you want to submit your feedback?", "Confirm Submission", "No", "Yes") == "Yes")
var/author_text = my_client.ckey
if(can_be_private() && feedback_hide_author)
author_text = md5(my_client.ckey + SSsqlite.get_feedback_pepper())
var/success = SSsqlite.insert_feedback(author = author_text, topic = feedback_topic, content = feedback_body, sqlite_object = SSsqlite.sqlite_db)
if(!success)
to_chat(my_client, span("warning", "Something went wrong while inserting your feedback into the database. Please try again. \
If this happens again, you should contact a developer."))
return
my_client.mob << browse(null, "window=[browser_id]") // Closes the window.
if(istype(my_client.mob, /mob/new_player))
var/mob/new_player/NP = my_client.mob
NP.new_player_panel_proc() // So the feedback button goes away, if the user gets put on cooldown.
qdel(src)

View File

@@ -0,0 +1,162 @@
/client
var/datum/managed_browser/feedback_viewer/feedback_viewer = null
/datum/admins/proc/view_feedback()
set category = "Admin"
set name = "View Feedback"
set desc = "Open the Feedback Viewer"
if(!check_rights(R_ADMIN|R_DEBUG))
return
if(usr.client.feedback_viewer)
usr.client.feedback_viewer.display()
else
usr.client.feedback_viewer = new(usr.client)
// This object holds the code to run the admin feedback viewer.
/datum/managed_browser/feedback_viewer
base_browser_id = "feedback_viewer"
title = "Submitted Feedback"
size_x = 900
size_y = 500
var/database/query/last_query = null
/datum/managed_browser/feedback_viewer/New(client/new_client)
if(!check_rights(R_ADMIN|R_DEBUG, new_client)) // Just in case someone figures out a way to spawn this as non-staff.
message_admins("[new_client] tried to view feedback with insufficent permissions.")
qdel(src)
..()
/datum/managed_browser/feedback_viewer/Destroy()
if(my_client)
my_client.feedback_viewer = null
return ..()
/datum/managed_browser/feedback_viewer/proc/feedback_filter(row_name, thing_to_find, exact = FALSE)
var/database/query/query = null
if(exact) // Useful for ID searches, so searching for 'id 10' doesn't also get 'id 101'.
query = new({"
SELECT *
FROM [SQLITE_TABLE_FEEDBACK]
WHERE [row_name] == ?
ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
DESC LIMIT 50;
"},
thing_to_find
)
else
// Wrap the thing in %s so LIKE will work.
thing_to_find = "%[thing_to_find]%"
query = new({"
SELECT *
FROM [SQLITE_TABLE_FEEDBACK]
WHERE [row_name] LIKE ?
ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
DESC LIMIT 50;
"},
thing_to_find
)
query.Execute(SSsqlite.sqlite_db)
SSsqlite.sqlite_check_for_errors(query, "Admin Feedback Viewer - Filter by [row_name] to find [thing_to_find]")
return query
// Builds the window for players to review their feedback.
/datum/managed_browser/feedback_viewer/get_html()
var/list/dat = list("<html><body>")
if(!last_query) // If no query was done before, just show the most recent feedbacks.
var/database/query/query = new({"
SELECT *
FROM [SQLITE_TABLE_FEEDBACK]
ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
DESC LIMIT 50;
"}
)
query.Execute(SSsqlite.sqlite_db)
SSsqlite.sqlite_check_for_errors(query, "Admin Feedback Viewer")
last_query = query
dat += "<table border='1' style='width:100%'>"
dat += "<tr>"
dat += "<th>[href(src, list("filter_id" = 1), "ID")]</th>"
dat += "<th>[href(src, list("filter_topic" = 1), "Topic")]</th>"
dat += "<th>[href(src, list("filter_author" = 1), "Author")]</th>"
dat += "<th>[href(src, list("filter_content" = 1), "Content")]</th>"
dat += "<th>[href(src, list("filter_datetime" = 1), "Datetime")]</th>"
dat += "</tr>"
while(last_query.NextRow())
var/list/row_data = last_query.GetRowData()
dat += "<tr>"
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_ID]]</td>"
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_TOPIC]]</td>"
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR]]</td>" // TODO: Color this to make hashed keys more distinguishable.
var/text = row_data[SQLITE_FEEDBACK_COLUMN_CONTENT]
if(length(text) > 512)
text = href(src, list(
"show_full_feedback" = 1,
"feedback_author" = row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR],
"feedback_content" = row_data[SQLITE_FEEDBACK_COLUMN_CONTENT]
), "[copytext(text, 1, 64)]... ([length(text)])")
else
text = replacetext(text, "\n", "<br>")
dat += "<td>[text]</td>"
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]]</td>"
dat += "</tr>"
dat += "</table>"
dat += "</body></html>"
return dat.Join()
// Used to show the full version of feedback in a seperate window.
/datum/managed_browser/feedback_viewer/proc/display_big_feedback(author, text)
var/list/dat = list("<html><body>")
dat += replacetext(text, "\n", "<br>")
var/datum/browser/popup = new(my_client.mob, "feedback_big", "[author]'s Feedback", 480, 520, src)
popup.set_content(dat.Join())
popup.open()
/datum/managed_browser/feedback_viewer/Topic(href, href_list[])
if(!my_client)
return FALSE
if(href_list["close"]) // To avoid refreshing.
return
if(href_list["show_full_feedback"])
display_big_feedback(href_list["feedback_author"], href_list["feedback_content"])
return
if(href_list["filter_id"])
var/id_to_search = input(my_client, "Write feedback ID here.", "Filter by ID", null) as null|num
if(id_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_ID, id_to_search, TRUE)
if(href_list["filter_author"])
var/author_to_search = input(my_client, "Write desired key or hash here. Partial keys/hashes are allowed.", "Filter by Author", null) as null|text
if(author_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_AUTHOR, author_to_search)
if(href_list["filter_topic"])
var/topic_to_search = input(my_client, "Write desired topic here. Partial topics are allowed. \
\nThe current topics in the config are [english_list(config.sqlite_feedback_topics)].", "Filter by Topic", null) as null|text
if(topic_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_TOPIC, topic_to_search)
if(href_list["filter_content"])
var/content_to_search = input(my_client, "Write desired content to find here. Partial matches are allowed.", "Filter by Content", null) as null|message
if(content_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_CONTENT, content_to_search)
if(href_list["filter_datetime"])
var/datetime_to_search = input(my_client, "Write desired datetime. Partial matches are allowed.\n\
Format is 'YYYY-MM-DD HH:MM:SS'.", "Filter by Datetime", null) as null|text
if(datetime_to_search)
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_DATETIME, datetime_to_search)
// Refresh.
display()

View File

@@ -168,6 +168,14 @@
containername = "Magnetic weapon crate"
access = access_security
/datum/supply_pack/munitions/mshells
name = "Weapons - Magnetic Shells"
contains = list(/obj/item/weapon/magnetic_ammo = 3)
cost = 100
containertype = /obj/structure/closet/crate/secure/weapon
containername = "Magnetic ammunition crate"
access = access_security
/datum/supply_pack/munitions/shotgunammo
name = "Ammunition - Shotgun shells"
contains = list(

View File

@@ -32,8 +32,104 @@
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor crate"
/datum/supply_pack/security/carriersblack
name = "Armor - Black modular armor"
contains = list(
/obj/item/clothing/suit/armor/pcarrier,
/obj/item/clothing/accessory/armor/armguards,
/obj/item/clothing/accessory/armor/legguards,
/obj/item/clothing/accessory/storage/pouches,
)
cost = 30
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
/datum/supply_pack/security/carriersblue
name = "Armor - Blue modular armor"
contains = list(
/obj/item/clothing/suit/armor/pcarrier/blue,
/obj/item/clothing/accessory/armor/armguards/blue,
/obj/item/clothing/accessory/armor/legguards/blue,
/obj/item/clothing/accessory/storage/pouches/blue,
)
cost = 30
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
/datum/supply_pack/security/carriersgreen
name = "Armor - Blue modular armor"
contains = list(
/obj/item/clothing/suit/armor/pcarrier/green,
/obj/item/clothing/accessory/armor/armguards/green,
/obj/item/clothing/accessory/armor/legguards/green,
/obj/item/clothing/accessory/storage/pouches/green,
)
cost = 30
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
/datum/supply_pack/security/carriersnavy
name = "Armor - Navy modular armor"
contains = list(
/obj/item/clothing/suit/armor/pcarrier/navy,
/obj/item/clothing/accessory/armor/armguards/navy,
/obj/item/clothing/accessory/armor/legguards/navy,
/obj/item/clothing/accessory/storage/pouches/navy,
)
cost = 30
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
/datum/supply_pack/security/carrierstan
name = "Armor - Tan modular armor"
contains = list(
/obj/item/clothing/suit/armor/pcarrier/tan,
/obj/item/clothing/accessory/armor/armguards/tan,
/obj/item/clothing/accessory/armor/legguards/tan,
/obj/item/clothing/accessory/storage/pouches/tan,
)
cost = 30
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
/datum/supply_pack/security/armorplate
name = "Armor - Security light armor plate"
contains = list(
/obj/item/clothing/accessory/armor/armorplate,
)
cost = 5
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/security/armorplatestab
name = "Armor - Security stab armor plate"
contains = list(
/obj/item/clothing/accessory/armor/armorplate/stab,
)
cost = 10
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/security/armorplatemedium
name = "Armor - Security armor plate"
contains = list(
/obj/item/clothing/accessory/armor/armorplate/medium,
)
cost = 10
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/security/armorplatetac
name = "Armor - Security medium armor plate"
contains = list(
/obj/item/clothing/accessory/armor/armorplate/tactical,
)
cost = 15
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carriers
name = "Armor - Plate carriers"
name = "Armor - Surplus plate carriers"
num_contained = 5
contains = list(
/obj/item/clothing/suit/armor/pcarrier,
@@ -43,7 +139,7 @@
/obj/item/clothing/suit/armor/pcarrier/tan,
/obj/item/clothing/suit/armor/pcarrier/press
)
cost = 20
cost = 10
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
@@ -82,7 +178,7 @@
containername = "Helmet Covers crate"
/datum/supply_pack/randomised/security/armorplates
name = "Armor - Security armor plates"
name = "Armor - Surplus security armor plates"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/armor/armorplate,
@@ -96,12 +192,12 @@
/obj/item/clothing/accessory/armor/armorplate/riot,
/obj/item/clothing/accessory/armor/armorplate/bulletproof
)
cost = 50
cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carrierarms
name = "Armor - Security armguard attachments"
name = "Armor - Surplus security armguard attachments"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/armor/armguards,
@@ -113,12 +209,12 @@
/obj/item/clothing/accessory/armor/armguards/riot,
/obj/item/clothing/accessory/armor/armguards/bulletproof
)
cost = 50
cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carrierlegs
name = "Armor - Security legguard attachments"
name = "Armor - Surplus security legguard attachments"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/armor/legguards,
@@ -130,12 +226,12 @@
/obj/item/clothing/accessory/armor/legguards/riot,
/obj/item/clothing/accessory/armor/legguards/bulletproof
)
cost = 50
cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carrierbags
name = "Armor - Security pouch attachments"
name = "Armor - Surplus security pouch attachments"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/storage/pouches,
@@ -149,7 +245,7 @@
/obj/item/clothing/accessory/storage/pouches/large/green,
/obj/item/clothing/accessory/storage/pouches/large/tan
)
cost = 60
cost = 50
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"

View File

@@ -117,7 +117,7 @@ var/list/wireColours = list("red", "blue", "green", "darkred", "orange", "brown"
var/obj/item/I = L.get_active_hand()
holder.add_hiddenprint(L)
if(href_list["cut"]) // Toggles the cut/mend status
if(I.is_wirecutter())
if(I?.is_wirecutter())
var/colour = href_list["cut"]
CutWireColour(colour)
playsound(holder, I.usesound, 20, 1)

View File

@@ -83,54 +83,54 @@ var/global/list/PDA_Manifest = list()
var/isactive = t.fields["p_stat"]
var/department = 0
var/depthead = 0 // Department Heads will be placed at the top of their lists.
if(real_rank in command_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_COMMAND))
heads[++heads.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
depthead = 1
if(rank=="Colony Director" && heads.len != 1)
heads.Swap(1,heads.len)
if(real_rank in security_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_SECURITY))
sec[++sec.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(depthead && sec.len != 1)
sec.Swap(1,sec.len)
if(real_rank in engineering_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_ENGINEERING))
eng[++eng.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(depthead && eng.len != 1)
eng.Swap(1,eng.len)
if(real_rank in medical_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_MEDICAL))
med[++med.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(depthead && med.len != 1)
med.Swap(1,med.len)
if(real_rank in science_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_RESEARCH))
sci[++sci.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(depthead && sci.len != 1)
sci.Swap(1,sci.len)
if(real_rank in planet_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_PLANET))
pla[++pla.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(real_rank in cargo_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_CARGO))
car[++car.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(depthead && car.len != 1)
car.Swap(1,car.len)
if(real_rank in civilian_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_CARGO))
civ[++civ.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1
if(depthead && civ.len != 1)
civ.Swap(1,civ.len)
if(real_rank in nonhuman_positions)
if(SSjob.is_job_in_department(real_rank, DEPARTMENT_SYNTHETIC))
bot[++bot.len] = list("name" = name, "rank" = rank, "active" = isactive)
department = 1

View File

@@ -39,7 +39,7 @@ var/datum/antagonist/loyalists/loyalists
return
global_objectives = list()
for(var/mob/living/carbon/human/player in mob_list)
if(!player.mind || player.stat==2 || !(player.mind.assigned_role in command_positions))
if(!player.mind || player.stat==2 || !(SSjob.is_job_in_department(player.mind.assigned_role, DEPARTMENT_COMMAND)))
continue
var/datum/objective/protect/loyal_obj = new
loyal_obj.target = player.mind

View File

@@ -42,7 +42,7 @@ var/datum/antagonist/revolutionary/revs
return
global_objectives = list()
for(var/mob/living/carbon/human/player in mob_list)
if(!player.mind || player.stat==2 || !(player.mind.assigned_role in command_positions))
if(!player.mind || player.stat==2 || !(SSjob.is_job_in_department(player.mind.assigned_role, DEPARTMENT_COMMAND)))
continue
var/datum/objective/rev/rev_obj = new
rev_obj.target = player.mind

View File

@@ -4,7 +4,8 @@
/datum/job/assistant
title = "Assistant"
flag = ASSISTANT
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
sorting_order = -1
department_flag = CIVILIAN
faction = "Station"
total_positions = -1

View File

@@ -7,7 +7,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 1)
/datum/job/captain
title = "Colony Director"
flag = CAPTAIN
department = "Command"
departments = list(DEPARTMENT_COMMAND)
sorting_order = 3 // Above everyone.
head_position = 1
department_flag = ENGSEC
faction = "Station"
@@ -58,7 +59,8 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 1)
/datum/job/hop
title = "Head of Personnel"
flag = HOP
department = "Command"
departments = list(DEPARTMENT_CIVILIAN, DEPARTMENT_CARGO, DEPARTMENT_COMMAND)
sorting_order = 2 // Above the QM, below captain.
head_position = 1
department_flag = CIVILIAN
faction = "Station"
@@ -106,7 +108,7 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 1)
/datum/job/secretary
title = "Command Secretary"
flag = BRIDGE
department = "Command"
departments = list(DEPARTMENT_COMMAND)
head_position = 1
department_flag = CIVILIAN
faction = "Station"

View File

@@ -7,7 +7,7 @@
/datum/job/bartender
title = "Bartender"
flag = BARTENDER
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 2
@@ -38,7 +38,7 @@
/datum/job/chef
title = "Chef"
flag = CHEF
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 2
@@ -67,7 +67,7 @@
/datum/job/hydro
title = "Botanist"
flag = BOTANIST
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 2
@@ -96,7 +96,8 @@
/datum/job/qm
title = "Quartermaster"
flag = QUARTERMASTER
department = "Cargo"
departments = list(DEPARTMENT_CARGO)
sorting_order = 1 // QM is above the cargo techs, but below the HoP.
head_position = 1
department_flag = CIVILIAN
faction = "Station"
@@ -127,7 +128,7 @@
/datum/job/cargo_tech
title = "Cargo Technician"
flag = CARGOTECH
department = "Cargo"
departments = list(DEPARTMENT_CARGO)
department_flag = CIVILIAN
faction = "Station"
total_positions = 2
@@ -152,7 +153,7 @@
/datum/job/mining
title = "Shaft Miner"
flag = MINER
department = "Cargo"
departments = list(DEPARTMENT_CARGO)
department_flag = CIVILIAN
faction = "Station"
total_positions = 3
@@ -182,7 +183,7 @@
/datum/job/janitor
title = "Janitor"
flag = JANITOR
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 2
@@ -210,7 +211,7 @@
/datum/job/librarian
title = "Librarian"
flag = LIBRARIAN
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 1
@@ -244,7 +245,7 @@
/datum/job/lawyer
title = "Internal Affairs Agent"
flag = LAWYER
department = "Internal Affairs"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 2

View File

@@ -2,7 +2,7 @@
/datum/job/chaplain
title = "Chaplain"
flag = CHAPLAIN
department = "Civilian"
departments = list(DEPARTMENT_CIVILIAN)
department_flag = CIVILIAN
faction = "Station"
total_positions = 1

View File

@@ -0,0 +1,79 @@
// A datum that holds information about a specific department.
// It is held inside, and managed by, the SSjob subsystem automatically,
// just define a department, and put that department's name in one or more job datums' departments list.
/datum/department
var/name = "NOPE" // Name used in UIs, and the index for the department assoc list in SSjob.
var/short_name = "NO" // Shorter name, used for things like external Topic() responses.
var/color = "#000000" // Color to use in UIs to represent this department.
var/list/jobs = list() // Assoc list. Key is the job title, and the value is a reference to the job datum. Populated by SSjob subsystem.
var/sorting_order = 0 // Used to sort departments, e.g. Command always being on top.
var/visible = TRUE // If false, it should not show up on things like the manifest or ID computer.
var/assignable = TRUE // Similar for above, but only for ID computers and such. Used for silicon department.
var/centcom_only = FALSE
/datum/department/command
name = DEPARTMENT_COMMAND
short_name = "Heads"
color = "#3333FF"
sorting_order = 10
/datum/department/security
name = DEPARTMENT_SECURITY
short_name = "Sec"
color = "#8E0000"
sorting_order = 6
/datum/department/engineering
name = DEPARTMENT_ENGINEERING
short_name = "Eng"
color = "#B27300"
sorting_order = 5
/datum/department/medical
name = DEPARTMENT_MEDICAL
short_name = "Med"
color = "#006600"
sorting_order = 4
/datum/department/research
name = DEPARTMENT_RESEARCH
short_name = "Sci"
color = "#A65BA6"
sorting_order = 3
/datum/department/cargo
name = DEPARTMENT_CARGO
short_name = "Car"
color = "#BB9040"
sorting_order = 2
/datum/department/civilian
name = DEPARTMENT_CIVILIAN
short_name = "Civ"
color = "#A32800"
sorting_order = 1
// Mostly for if someone wanted to rewrite manifest code to be map-agnostic.
/datum/department/misc
name = "Miscellaneous"
short_name = "Misc"
color = "#666666"
sorting_order = 0
assignable = FALSE
/datum/department/synthetic
name = DEPARTMENT_SYNTHETIC
short_name = "Bot"
color = "#222222"
sorting_order = -1
assignable = FALSE
// This one isn't very useful since no real centcom jobs exist yet.
// Instead the jobs like ERT are hardcoded in.
/datum/department/centcom
name = "Central Command"
short_name = "Centcom"
color = "#A52A2A"
sorting_order = 20 // Above Command.
centcom_only = TRUE

View File

@@ -5,7 +5,8 @@
title = "Chief Engineer"
flag = CHIEF
head_position = 1
department = "Engineering"
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_COMMAND)
sorting_order = 2
department_flag = ENGSEC
faction = "Station"
total_positions = 1
@@ -44,7 +45,7 @@
/datum/job/engineer
title = "Station Engineer"
flag = ENGINEER
department = "Engineering"
departments = list(DEPARTMENT_ENGINEERING)
department_flag = ENGSEC
faction = "Station"
total_positions = 5
@@ -88,7 +89,7 @@
/datum/job/atmos
title = "Atmospheric Technician"
flag = ATMOSTECH
department = "Engineering"
departments = list(DEPARTMENT_ENGINEERING)
department_flag = ENGSEC
faction = "Station"
total_positions = 3

View File

@@ -16,8 +16,10 @@
var/list/alt_titles = list() // List of alternate titles; if a job has alt-titles, it MUST have one for the base job
var/req_admin_notify // If this is set to 1, a text is printed to the player when jobs are assigned, telling him that he should let admins know that he has to disconnect.
var/minimal_player_age = 0 // 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/department = null // Does this position have a department tag?
var/list/departments = list() // List of departments this job belongs to, if any. The first one on the list will be the 'primary' department.
var/sorting_order = 0 // Used for sorting jobs so boss jobs go above regular ones, and their boss's boss is above that. Higher numbers = higher in sorting.
var/head_position = 0 // Is this position Command?
var/assignable = TRUE // Should it show up on things like the ID computer?
var/minimum_character_age = 0
var/ideal_character_age = 30
var/has_headset = TRUE //Do people with this job need to be given headsets and told how to use them? E.g. Cyborgs don't.

View File

@@ -5,7 +5,8 @@
title = "Chief Medical Officer"
flag = CMO
head_position = 1
department = "Medical"
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_COMMAND)
sorting_order = 2
department_flag = MEDSCI
faction = "Station"
total_positions = 1
@@ -41,7 +42,7 @@
/datum/job/doctor
title = "Medical Doctor"
flag = DOCTOR
department = "Medical"
departments = list(DEPARTMENT_MEDICAL)
department_flag = MEDSCI
faction = "Station"
total_positions = 5
@@ -98,7 +99,7 @@
/datum/job/chemist
title = "Chemist"
flag = CHEMIST
department = "Medical"
departments = list(DEPARTMENT_MEDICAL)
department_flag = MEDSCI
faction = "Station"
total_positions = 2
@@ -130,7 +131,7 @@
/datum/job/geneticist
title = "Geneticist"
flag = GENETICIST
department = "Medical"
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_RESEARCH)
department_flag = MEDSCI
faction = "Station"
total_positions = 0
@@ -156,7 +157,7 @@
/datum/job/psychiatrist
title = "Psychiatrist"
flag = PSYCHIATRIST
department = "Medical"
departments = list(DEPARTMENT_MEDICAL)
department_flag = MEDSCI
faction = "Station"
total_positions = 1
@@ -187,7 +188,7 @@
/datum/job/paramedic
title = "Paramedic"
flag = PARAMEDIC
department = "Medical"
departments = list(DEPARTMENT_MEDICAL)
department_flag = MEDSCI
faction = "Station"
total_positions = 2

View File

@@ -5,7 +5,8 @@
title = "Research Director"
flag = RD
head_position = 1
department = "Science"
departments = list(DEPARTMENT_RESEARCH, DEPARTMENT_COMMAND)
sorting_order = 2
department_flag = MEDSCI
faction = "Station"
total_positions = 1
@@ -47,7 +48,7 @@
/datum/job/scientist
title = "Scientist"
flag = SCIENTIST
department = "Science"
departments = list(DEPARTMENT_RESEARCH)
department_flag = MEDSCI
faction = "Station"
total_positions = 5
@@ -93,7 +94,7 @@
/datum/job/xenobiologist
title = "Xenobiologist"
flag = XENOBIOLOGIST
department = "Science"
departments = list(DEPARTMENT_RESEARCH)
department_flag = MEDSCI
faction = "Station"
total_positions = 3
@@ -126,7 +127,7 @@
/datum/job/roboticist
title = "Roboticist"
flag = ROBOTICIST
department = "Science"
departments = list(DEPARTMENT_RESEARCH)
department_flag = MEDSCI
faction = "Station"
total_positions = 2

View File

@@ -5,7 +5,8 @@
title = "Head of Security"
flag = HOS
head_position = 1
department = "Security"
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_COMMAND)
sorting_order = 2
department_flag = ENGSEC
faction = "Station"
total_positions = 1
@@ -47,7 +48,8 @@
/datum/job/warden
title = "Warden"
flag = WARDEN
department = "Security"
departments = list(DEPARTMENT_SECURITY)
sorting_order = 1
department_flag = ENGSEC
faction = "Station"
total_positions = 1
@@ -75,7 +77,7 @@
/datum/job/detective
title = "Detective"
flag = DETECTIVE
department = "Security"
departments = list(DEPARTMENT_SECURITY)
department_flag = ENGSEC
faction = "Station"
total_positions = 2
@@ -107,7 +109,7 @@
/datum/job/officer
title = "Security Officer"
flag = OFFICER
department = "Security"
departments = list(DEPARTMENT_SECURITY)
department_flag = ENGSEC
faction = "Station"
total_positions = 4

View File

@@ -4,6 +4,8 @@
/datum/job/ai
title = "AI"
flag = AI
departments = list(DEPARTMENT_SYNTHETIC)
sorting_order = 1 // Be above their borgs.
department_flag = ENGSEC
faction = "Station"
total_positions = 0 // Not used for AI, see is_position_available below and modules/mob/living/silicon/ai/latejoin.dm
@@ -15,6 +17,7 @@
account_allowed = 0
economic_modifier = 0
has_headset = FALSE
assignable = FALSE
outfit_type = /decl/hierarchy/outfit/job/silicon/ai
job_description = "The AI oversees the operation of the station and its crew, but has no real authority over them. \
The AI is required to follow its Laws, and Lawbound Synthetics that are linked to it are expected to follow \
@@ -43,6 +46,7 @@
/datum/job/cyborg
title = "Cyborg"
flag = CYBORG
departments = list(DEPARTMENT_SYNTHETIC)
department_flag = ENGSEC
faction = "Station"
total_positions = 2
@@ -53,6 +57,7 @@
account_allowed = 0
economic_modifier = 0
has_headset = FALSE
assignable = FALSE
outfit_type = /decl/hierarchy/outfit/job/silicon/cyborg
job_description = "A Cyborg is a mobile station synthetic, piloted by a cybernetically preserved brain. It is considered a person, but is still required \
to follow its Laws."

View File

@@ -114,7 +114,7 @@ var/global/datum/controller/occupations/job_master
if(istype(job, GetJob("Assistant"))) // We don't want to give him assistant, that's boring!
continue
if(job.title in command_positions) //If you want a command position, select it!
if(SSjob.is_job_in_department(job.title, DEPARTMENT_COMMAND)) //If you want a command position, select it!
continue
if(jobban_isbanned(player, job.title))
@@ -144,7 +144,7 @@ var/global/datum/controller/occupations/job_master
///This proc is called before the level loop of DivideOccupations() and will try to select a head, ignoring ALL non-head preferences for every level until it locates a head or runs out of levels to check
proc/FillHeadPosition()
for(var/level = 1 to 3)
for(var/command_position in command_positions)
for(var/command_position in SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND))
var/datum/job/job = GetJob(command_position)
if(!job) continue
var/list/candidates = FindOccupationCandidates(job, level)
@@ -184,7 +184,7 @@ var/global/datum/controller/occupations/job_master
///This proc is called at the start of the level loop of DivideOccupations() and will cause head jobs to be checked before any other jobs of the same level
proc/CheckHeadPositions(var/level)
for(var/command_position in command_positions)
for(var/command_position in SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND))
var/datum/job/job = GetJob(command_position)
if(!job) continue
var/list/candidates = FindOccupationCandidates(job, level)
@@ -422,9 +422,9 @@ var/global/datum/controller/occupations/job_master
log_game("JOINED [key_name(H)] as \"[rank]\"")
// If they're head, give them the account info for their department
if(H.mind && job.head_position)
if(H.mind && job.head_position && LAZYLEN(job.departments))
var/remembered_info = ""
var/datum/money_account/department_account = department_accounts[job.department]
var/datum/money_account/department_account = department_accounts[job.departments[1]]
if(department_account)
remembered_info += "<b>Your department's account number is:</b> #[department_account.account_number]<br>"

View File

@@ -44,91 +44,8 @@ var/const/CHAPLAIN =(1<<10)
var/const/ASSISTANT =(1<<11)
var/const/BRIDGE =(1<<12)
var/list/assistant_occupations = list(
)
var/list/command_positions = list(
"Colony Director",
"Head of Personnel",
"Head of Security",
"Chief Engineer",
"Research Director",
"Chief Medical Officer",
"Command Secretary"
)
var/list/engineering_positions = list(
"Chief Engineer",
"Station Engineer",
"Atmospheric Technician",
)
var/list/medical_positions = list(
"Chief Medical Officer",
"Medical Doctor",
"Geneticist",
"Psychiatrist",
"Chemist",
"Paramedic"
)
var/list/science_positions = list(
"Research Director",
"Scientist",
"Geneticist", //Part of both medical and science
"Roboticist",
"Xenobiologist"
)
//BS12 EDIT
var/list/cargo_positions = list(
"Quartermaster",
"Cargo Technician",
"Shaft Miner"
)
var/list/civilian_positions = list(
"Head of Personnel",
"Bartender",
"Botanist",
"Chef",
"Janitor",
"Librarian",
"Lawyer",
"Chaplain",
"Assistant"
)
var/list/security_positions = list(
"Head of Security",
"Warden",
"Detective",
"Security Officer"
)
var/list/planet_positions = list(
"Explorer",
"Pilot",
"Search and Rescue"
)
var/list/nonhuman_positions = list(
"AI",
"Cyborg",
"pAI"
)
/proc/guest_jobbans(var/job)
return ((job in command_positions) || (job in nonhuman_positions) || (job in security_positions))
return ( (job in SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND)) || (job in SSjob.get_job_titles_in_department(DEPARTMENT_SYNTHETIC)) || (job in SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY)) )
/proc/get_job_datums()
var/list/occupations = list()

View File

@@ -1,7 +1,7 @@
/obj/machinery/ai_slipper
name = "\improper AI Liquid Dispenser"
icon = 'icons/obj/device.dmi'
icon_state = "motion0"
icon_state = "liquid_dispenser"
anchored = 1.0
use_power = 1
idle_power_usage = 10
@@ -24,9 +24,9 @@
/obj/machinery/ai_slipper/update_icon()
if(stat & NOPOWER || stat & BROKEN)
icon_state = "motion0"
icon_state = "liquid_dispenser"
else
icon_state = disabled ? "motion0" : "motion3"
icon_state = disabled ? "liquid_dispenser" : "liquid_dispenser_on"
/obj/machinery/ai_slipper/proc/setState(var/enabled, var/uses)
disabled = disabled

View File

@@ -841,7 +841,7 @@
var/chancetokill = 30*traitors_aboard-(5*alive) //eg: 30*2-(10) = 50%, 2 traitorss, 2 crew is 50% chance
if(prob(chancetokill))
var/deadguy = remove_crewmember()
eventdat += "<br>The traitor[trait2 ? "s":""] run[trait2 ? "":"s"] up to [deadguy] and murder them!"
eventdat += "<br>The traitor[trait2 ? "s":""] run[trait2 ? "":"s"] up to [deadguy] and murder[trait2 ? "" : "s"] them!"
else
eventdat += "<br>You valiantly fight off the traitor[trait2 ? "s":""]!"
eventdat += "<br>You cut the traitor[trait2 ? "s":""] up into meat... Eww"

View File

@@ -101,17 +101,18 @@
data["centcom_access"] = is_centcom()
data["all_centcom_access"] = null
data["regions"] = null
data["id_rank"] = modify && modify.assignment ? modify.assignment : "Unassigned"
data["jobs"] = list(
list("cat" = "Engineering", "jobs" = format_jobs(engineering_positions)),
list("cat" = "Medical", "jobs" = format_jobs(medical_positions)),
list("cat" = "Science", "jobs" = format_jobs(science_positions)),
list("cat" = "Security", "jobs" = format_jobs(security_positions)),
list("cat" = "Cargo", "jobs" = format_jobs(cargo_positions)),
list("cat" = "Planetside", "jobs" = format_jobs(planet_positions)),
list("cat" = "Civilian", "jobs" = format_jobs(civilian_positions)),
list("cat" = "CentCom", "jobs" = format_jobs(get_all_centcom_jobs()))
)
var/list/departments = list()
for(var/D in SSjob.get_all_department_datums())
var/datum/department/dept = D
if(!dept.assignable) // No AI ID cards for you.
continue
if(dept.centcom_only && !is_centcom())
continue
departments[++departments.len] = list("department_name" = dept.name, "jobs" = format_jobs(SSjob.get_job_titles_in_department(dept.name)) )
data["departments"] = departments
if (modify && is_centcom())
var/list/all_centcom_access = list()
@@ -208,16 +209,10 @@
if(is_centcom())
access = get_centcom_access(t1)
else
var/datum/job/jobdatum
for(var/jobtype in typesof(/datum/job))
var/datum/job/J = new jobtype
if(ckey(J.title) == ckey(t1))
jobdatum = J
break
var/datum/job/jobdatum = SSjob.get_job(t1)
if(!jobdatum)
to_chat(usr, "<span class='warning'>No log exists for this job: [t1]</span>")
return
access = jobdatum.get_access()
modify.access = access

View File

@@ -15,6 +15,7 @@
var/backup_author = ""
var/icon/backup_img = null
var/icon/backup_caption = ""
var/post_time = 0
/datum/feed_channel
var/channel_name=""
@@ -78,6 +79,7 @@
newMsg.body = msg
newMsg.time_stamp = "[stationtime2text()]"
newMsg.is_admin_message = adminMessage
newMsg.post_time = round_duration_in_ticks // Should be almost universally unique
if(message_type)
newMsg.message_type = message_type
if(photo)

View File

@@ -10,7 +10,7 @@
active_power_usage = 40000 //40 kW
var/efficiency = 40000 //will provide the modified power rate when upgraded
var/obj/item/charging = null
var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/modular_computer, /obj/item/weapon/computer_hardware/battery_module, /obj/item/weapon/cell, /obj/item/device/flashlight, /obj/item/device/electronic_assembly, /obj/item/weapon/weldingtool/electric, /obj/item/ammo_magazine/smart, /obj/item/device/flash)
var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/modular_computer, /obj/item/weapon/computer_hardware/battery_module, /obj/item/weapon/cell, /obj/item/device/flashlight, /obj/item/device/electronic_assembly, /obj/item/weapon/weldingtool/electric, /obj/item/ammo_magazine/smart, /obj/item/device/flash, /obj/item/device/defib_kit)
var/icon_state_charged = "recharger2"
var/icon_state_charging = "recharger1"
var/icon_state_idle = "recharger0" //also when unpowered
@@ -72,7 +72,7 @@
if(EW.use_external_power)
to_chat(user, "<span class='notice'>\The [EW] has no recharge port.</span>")
return
else if(!G.get_cell())
if(!G.get_cell())
to_chat(user, "\The [G] does not have a battery installed.")
return
@@ -125,27 +125,6 @@
update_use_power(1)
icon_state = icon_state_idle
else
if(istype(charging, /obj/item/modular_computer))
var/obj/item/modular_computer/C = charging
if(!C.battery_module.battery.fully_charged())
icon_state = icon_state_charging
C.battery_module.battery.give(CELLRATE*efficiency)
update_use_power(2)
else
icon_state = icon_state_charged
update_use_power(1)
return
else if(istype(charging, /obj/item/weapon/computer_hardware/battery_module))
var/obj/item/weapon/computer_hardware/battery_module/BM = charging
if(!BM.battery.fully_charged())
icon_state = icon_state_charging
BM.battery.give(CELLRATE*efficiency)
update_use_power(2)
else
icon_state = icon_state_charged
update_use_power(1)
return
var/obj/item/weapon/cell/C = charging.get_cell()
if(istype(C))
if(!C.fully_charged())

View File

@@ -1123,7 +1123,8 @@
/obj/item/toy/plushie/face_hugger = 1,
/obj/item/toy/plushie/carp = 1,
/obj/item/toy/plushie/deer = 1,
/obj/item/toy/plushie/tabby_cat = 1)
/obj/item/toy/plushie/tabby_cat = 1,
/obj/item/device/threadneedle = 3)
premium = list(/obj/item/weapon/reagent_containers/food/drinks/bottle/champagne = 1,
/obj/item/weapon/storage/trinketbox = 2)
prices = list(/obj/item/weapon/storage/fancy/heartbox = 15,
@@ -1151,7 +1152,8 @@
/obj/item/toy/plushie/face_hugger = 50,
/obj/item/toy/plushie/carp = 50,
/obj/item/toy/plushie/deer = 50,
/obj/item/toy/plushie/tabby_cat = 50)
/obj/item/toy/plushie/tabby_cat = 50,
/obj/item/device/threadneedle = 2)
/obj/machinery/vending/fishing
name = "Loot Trawler"

View File

@@ -72,7 +72,7 @@
var/obj/item/projectile/P = A
P.dispersion = deviation
process_accuracy(P, chassis.occupant, target)
P.launch_projectile_from_turf(target, chassis.occupant.zone_sel.selecting, chassis.occupant, params)
P.launch_projectile_from_turf(target, chassis.get_pilot_zone_sel(), chassis.occupant, params)
else if(istype(A, /atom/movable))
var/atom/movable/AM = A
AM.throw_at(target, 7, 1, chassis)

View File

@@ -0,0 +1,11 @@
/*
* Helper file for Exosuit / Mecha code.
*/
// Returns, at least, a usable target body position, for things like guns.
/obj/mecha/proc/get_pilot_zone_sel()
if(!occupant || !occupant.zone_sel || occupant.stat)
return BP_TORSO
return occupant.zone_sel.selecting

View File

@@ -120,6 +120,7 @@
// step_towards(M, src)
. = buckle_mob(M, forced)
playsound(src.loc, 'sound/effects/seatbelt.ogg', 50, 1)
if(.)
if(!silent)
if(M == user)
@@ -135,6 +136,7 @@
/atom/movable/proc/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
var/mob/living/M = unbuckle_mob(buckled_mob)
playsound(src.loc, 'sound/effects/seatbelt.ogg', 50, 1)
if(M)
if(M != user)
M.visible_message(\

View File

@@ -207,3 +207,6 @@
qdel(src)
else
..()
/obj/item/weapon/pen/crayon/attack_self(var/mob/user)
return

View File

@@ -439,6 +439,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
if(2) icon = 'icons/obj/pda_slim.dmi'
if(3) icon = 'icons/obj/pda_old.dmi'
if(4) icon = 'icons/obj/pda_rugged.dmi'
if(5) icon = 'icons/obj/pda_holo.dmi'
else
icon = 'icons/obj/pda_old.dmi'
log_debug("Invalid switch for PDA, defaulting to old PDA icons. [pdachoice] chosen.")

View File

@@ -122,6 +122,8 @@
data["manifest"] = PDA_Manifest
data["feeds"] = compile_news()
data["latest_news"] = get_recent_news()
if(newsfeed_channel)
data["target_feed"] = data["feeds"][newsfeed_channel]
if(cartridge) // If there's a cartridge, we need to grab the information from it
data["cart_devices"] = cartridge.get_device_status()
data["cart_templates"] = cartridge.ui_templates
@@ -280,6 +282,9 @@
var/obj/O = cartridge.internal_devices[text2num(href_list["toggle_device"])]
cartridge.active_devices ^= list(O) // Exclusive or, will toggle its presence
if(href_list["newsfeed"])
newsfeed_channel = text2num(href_list["newsfeed"])
if(href_list["cartridge_topic"] && cartridge) // Has to have a cartridge to perform these functions
cartridge.Topic(href, href_list)

View File

@@ -72,6 +72,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
var/datum/exonet_protocol/exonet = null
var/list/communicating = list()
var/update_ticks = 0
var/newsfeed_channel = 0
// Proc: New()
// Parameters: None

View File

@@ -62,7 +62,8 @@
"name" = channel.channel_name,
"censored" = channel.censored,
"author" = channel.author,
"messages" = messages
"messages" = messages,
"index" = feeds.len + 1 // actually align them, since I guess the population of the list doesn't occur until after the evaluation of the new entry's contents
)
return feeds
@@ -85,14 +86,14 @@
"time_stamp" = FM.time_stamp,
"has_image" = (FM.img != null),
"caption" = FM.caption,
"time" = FM.post_time
)
// Cut out all but the youngest three
while(news.len > 3)
var/oldest = min(news[0]["time_stamp"], news[1]["time_stamp"], news[2]["time_stamp"], news[3]["time_stamp"])
for(var/i = 0, i < 4, i++)
if(news[i]["time_stamp"] == oldest)
news.Remove(news[i])
if(news.len > 3)
sortByKey(news, "time")
news.Cut(1, news.len - 2) // Last three have largest timestamps, youngest posts
news.Swap(1, 3) // List is sorted in ascending order of timestamp, we want descending
return news

View File

@@ -19,6 +19,9 @@
var/obj/item/weapon/shockpaddles/linked/paddles
var/obj/item/weapon/cell/bcell = null
/obj/item/device/defib_kit/get_cell()
return bcell
/obj/item/device/defib_kit/New() //starts without a cell for rnd
..()
if(ispath(paddles))

View File

@@ -3,10 +3,30 @@
icon = 'icons/obj/objects.dmi'
desc = "This is definitely something cool."
/datum/category_item/catalogue/information/objects/pascalb
name = "Object - Pascal B Steel Shaft Cap"
desc = "In the year 1957, the United States of America - an Earth nation - performed a series \
of earth nuclear weapons tests codenamed 'Operation Plumbbob', which remain the largest and \
longest running nuclear test series performed on the American continent. Test data included \
various altitude detonations, effects on several materials and structures at various \
distances, and the effects of radiation on military hardware and the human body. \
<br><br>\
On the 27th of August that year, in a test named 'Pascal-B' a 300t nuclear payload \
was buried in a shaft capped by a 900kg steel plate cap. The test was intended to \
verify the safety of underground detonation, but the shaft was not sufficient to \
contain the shockwave. According to experiment designer Robert Brownlee, the steel \
cap was propelled upwards at a velocity of 240,000km/h - over six times Earth's \
escape velocity. The cap appeared in only one frame of high-speed camera recording. \
<br><br>\
It had been theorized that the cap had exited earth's atmosphere and entered orbit. \
It would seem the cap traveled farther than had been possibly imagined."
value = CATALOGUER_REWARD_MEDIUM
/obj/item/poi/pascalb
icon_state = "pascalb"
name = "misshapen manhole cover"
desc = "The top of this twisted chunk of metal is faintly stamped with a five pointed star. 'Property of US Army, Pascal B - 1957'."
catalogue_data = list(/datum/category_item/catalogue/information/objects/pascalb)
/obj/item/poi/pascalb/New()
START_PROCESSING(SSobj, src)
@@ -19,6 +39,25 @@
STOP_PROCESSING(SSobj, src)
return ..()
/datum/category_item/catalogue/information/objects/oldreactor
name = "Object - 24th Century Fission Reactor Rack"
desc = "Prior to the discovery of Phoron in 2380, and the development of the hydrophoron \
supermatter reactor, most spacecraft operated on nuclear fission reactors, using processed \
radioactive material as fuel. While the design had been near-perfected by the 24th century, \
with some models capable of holding hundreds of fuel rods at one time and operating almost \
unsupervised for weeks at a time.\
<br><br>\
However, as accidents were not uncommon due to the inherent dangers of space travel and the \
nature of reactor racks such as this one fully containing the unstable fuel material, many \
fission vessels were built capable of jettisoning their entire engine sections as it was seen \
as preferable to evacuating a ship's crew and potentially losing the entire craft and its cargo. \
<br><br>\
VifGov records indicate that the colony ship ICV Kauai declared a major onboard emergency in Sif orbit \
on the 14th April 2353, citing major systems malfunction following a fire in the engine compartment. \
Due to the relatively sparse population of the planet, it was deemed safe to jettison both engine \
blocks, and the colonists were safely towed to port with no hands lost."
value = CATALOGUER_REWARD_MEDIUM
/obj/structure/closet/crate/oldreactor
name = "fission reactor rack"
desc = "Used in older models of nuclear reactors, essentially a cooling rack for high volumes of radioactive material."
@@ -26,6 +65,7 @@
icon_state = "poireactor"
icon_opened = "poireactor_open"
icon_closed = "poireactor"
catalogue_data = list(/datum/category_item/catalogue/information/objects/oldreactor)
climbable = 0
starts_with = list(
@@ -35,6 +75,7 @@
icon_state = "poireactor_broken"
name = "ruptured fission reactor rack"
desc = "This broken hunk of machinery looks extremely dangerous."
catalogue_data = list(/datum/category_item/catalogue/information/objects/oldreactor)
/obj/item/poi/brokenoldreactor/New()
START_PROCESSING(SSobj, src)
@@ -47,3 +88,29 @@
STOP_PROCESSING(SSobj, src)
return ..()
/datum/category_item/catalogue/information/objects/growthcanister
name = "Object - Growth Inhibitor 78-1"
desc = "The production of Vatborn humans is a process which involves the synthesis of over two hundred \
distinct chemical compounds. While most Vatborn are 'produced' as infants and merely genetically modified \
to encourage rapid early maturation, the specific development of the controversial 'Expedited' Vatborn calls for \
a far more intensive process.\
<br><br>\
Growth Inhibitor Type 78-1 is used in the rapid artificial maturation process to prevent the 'overdevelopment' of\
particular cell structures in the Vatborn's body, halting the otherwise inevitable development of aggressive cancerous\
growths which would be detrimental or lethal to the subject. Exposure to the compound in its pure form can cause\
devastating damage to living tissue, ceasing all regenerative activity in an organism's cells. While immediate effects\
can be halted by recent medical innovations, exposure can severely shorten a sapient's life expectancy.\
<br><br>\
In early 2564, the NanoTrasen corporation was implicated in the accidental spillage of over a dozen full cargo containers\
of Growth Inhibitor 78-1 in the Ullran Expanse of Sif, and were charged by the Sif Environmental Agency with extreme \
environmental damage and neglect."
value = CATALOGUER_REWARD_MEDIUM
/obj/structure/prop/poicanister
name = "Ruptured Chemical Canister"
desc = "A cracked open chemical canister labelled 'Growth Inhibitor 78-1'"
icon = 'icons/obj/atmos.dmi'
icon_state = "yellow-1"
catalogue_data = list(/datum/category_item/catalogue/information/objects/growthcanister)
anchored = 0
density = 1

View File

@@ -0,0 +1,7 @@
// I like the idea of this item having more uses in future.
/obj/item/device/threadneedle
name = "thread and needle"
icon = 'icons/obj/items.dmi'
icon_state = "needle_thread"
desc = "Used for most sewing and tailoring applications."

View File

@@ -874,9 +874,31 @@
anchored = 0
density = 1
var/phrase = "I don't want to exist anymore!"
var/searching = FALSE
var/opened = FALSE // has this been slit open? this will allow you to store an object in a plushie.
var/obj/item/stored_item // Note: Stored items can't be bigger than the plushie itself.
/obj/structure/plushie/examine(mob/user)
..()
if(opened)
to_chat(user, "<i>You notice an incision has been made on [src].</i>")
if(in_range(user, src) && stored_item)
to_chat(user, "<i>You can see something in there...</i>")
/obj/structure/plushie/attack_hand(mob/user)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
if(stored_item && !searching)
searching = TRUE
if(do_after(user, 10))
to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
stored_item.forceMove(get_turf(src))
stored_item = null
searching = FALSE
return
else
searching = FALSE
if(user.a_intent == I_HELP)
user.visible_message("<span class='notice'><b>\The [user]</b> hugs [src]!</span>","<span class='notice'>You hug [src]!</span>")
else if (user.a_intent == I_HURT)
@@ -888,6 +910,34 @@
visible_message("[src] says, \"[phrase]\"")
/obj/structure/plushie/attackby(obj/item/I as obj, mob/user as mob)
if(istype(I, /obj/item/device/threadneedle) && opened)
to_chat(user, "You sew the hole in [src].")
opened = FALSE
return
if(is_sharp(I) && !opened)
to_chat(user, "You open a small incision in [src]. You can place tiny items inside.")
opened = TRUE
return
if(opened)
if(stored_item)
to_chat(user, "There is already something in here.")
return
if(!(I.w_class > w_class))
to_chat(user, "You place [I] inside [src].")
user.drop_from_inventory(I, src)
I.forceMove(src)
stored_item = I
return
else
to_chat(user, "You open a small incision in [src]. You can place tiny items inside.")
..()
/obj/structure/plushie/ian
name = "plush corgi"
desc = "A plushie of an adorable corgi! Don't you just want to hug it and squeeze it and call it \"Ian\"?"
@@ -921,8 +971,30 @@
w_class = ITEMSIZE_TINY
var/last_message = 0
var/pokephrase = "Uww!"
var/searching = FALSE
var/opened = FALSE // has this been slit open? this will allow you to store an object in a plushie.
var/obj/item/stored_item // Note: Stored items can't be bigger than the plushie itself.
/obj/item/toy/plushie/examine(mob/user)
..()
if(opened)
to_chat(user, "<i>You notice an incision has been made on [src].</i>")
if(in_range(user, src) && stored_item)
to_chat(user, "<i>You can see something in there...</i>")
/obj/item/toy/plushie/attack_self(mob/user as mob)
if(stored_item && !searching)
searching = TRUE
if(do_after(user, 10))
to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
stored_item.forceMove(get_turf(src))
stored_item = null
searching = FALSE
return
else
searching = FALSE
if(world.time - last_message <= 1 SECOND)
return
if(user.a_intent == I_HELP)
@@ -955,6 +1027,31 @@
if(istype(I, /obj/item/toy/plushie) || istype(I, /obj/item/organ/external/head))
user.visible_message("<span class='notice'>[user] makes \the [I] kiss \the [src]!.</span>", \
"<span class='notice'>You make \the [I] kiss \the [src]!.</span>")
return
if(istype(I, /obj/item/device/threadneedle) && opened)
to_chat(user, "You sew the hole underneath [src].")
opened = FALSE
return
if(is_sharp(I) && !opened)
to_chat(user, "You open a small incision in [src]. You can place tiny items inside.")
opened = TRUE
return
if( (!(I.w_class > w_class)) && opened)
if(stored_item)
to_chat(user, "There is already something in here.")
return
to_chat(user, "You place [I] inside [src].")
user.drop_from_inventory(I, src)
I.forceMove(src)
stored_item = I
to_chat(user, "You placed [I] into [src].")
return
return ..()
/obj/item/toy/plushie/nymph

View File

@@ -0,0 +1,72 @@
/obj/item/trash/material
icon = 'icons/obj/material_trash.dmi'
matter = list()
var/matter_chances = list() //List of lists: list(mat_name, chance, amount)
/obj/item/trash/material/Initialize()
. = ..()
if(!matter)
matter = list()
for(var/list/L in matter_chances)
if(prob(L[2]))
matter |= L[1]
matter[L[1]] += max(0, L[3] + rand(-2,2))
/obj/item/trash/material/metal
name = "scrap metal"
desc = "A piece of metal that can be recycled in an autolathe."
icon_state = "metal0"
matter_chances = list(
list(MAT_STEEL, 100, 15),
list(MAT_STEEL, 50, 10),
list(MAT_STEEL, 10, 20),
list(MAT_PLASTEEL, 10, 5),
list(MAT_PLASTEEL, 5, 10)
)
/obj/item/trash/material/metal/Initialize()
. = ..()
icon_state = "metal[rand(4)]"
/obj/item/trash/material/circuit
name = "burnt circuit"
desc = "A burnt circuit that can be recycled in an autolathe."
w_class = ITEMSIZE_SMALL
icon_state = "circuit0"
matter_chances = list(
list(MAT_GLASS, 100, 4),
list(MAT_GLASS, 50, 3),
list(MAT_PLASTIC, 40, 3),
list(MAT_SILVER, 18, 3),
list(MAT_GOLD, 17, 3),
list(MAT_DIAMOND, 4, 2),
)
/obj/item/trash/material/circuit/Initialize()
. = ..()
icon_state = "circuit[rand(3)]"
/obj/item/trash/material/device
name = "broken device"
desc = "A broken device that can be recycled in an autolathe."
w_class = ITEMSIZE_SMALL
icon_state = "device0"
matter_chances = list(
list(MAT_STEEL, 100, 10),
list(MAT_GLASS, 90, 7),
list(MAT_PLASTIC, 100, 10),
list(MAT_SILVER, 16, 7),
list(MAT_GOLD, 15, 5),
list(MAT_DIAMOND, 5, 2),
)
/obj/item/trash/material/device/Initialize()
. = ..()
icon_state = "device[rand(3)]"

View File

@@ -505,6 +505,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM
slot_flags = SLOT_BELT
attack_verb = list("burnt", "singed")
var/base_state
var/activation_sound = 'sound/items/lighter_on.ogg'
var/deactivation_sound = 'sound/items/lighter_off.ogg'
/obj/item/weapon/flame/lighter/zippo
name = "\improper Zippo lighter"
@@ -512,6 +514,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM
icon = 'icons/obj/zippo.dmi'
icon_state = "zippo"
item_state = "zippo"
activation_sound = 'sound/items/zippo_on.ogg'
deactivation_sound = 'sound/items/zippo_off.ogg'
/obj/item/weapon/flame/lighter/random
New()
@@ -526,6 +530,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
lit = 1
icon_state = "[base_state]on"
item_state = "[base_state]on"
playsound(src.loc, activation_sound, 75, 1)
if(istype(src, /obj/item/weapon/flame/lighter/zippo) )
user.visible_message("<span class='rose'>Without even breaking stride, [user] flips open and lights [src] in one smooth movement.</span>")
else
@@ -545,6 +550,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
lit = 0
icon_state = "[base_state]"
item_state = "[base_state]"
playsound(src.loc, deactivation_sound, 75, 1)
if(istype(src, /obj/item/weapon/flame/lighter/zippo) )
user.visible_message("<span class='rose'>You hear a quiet click, as [user] shuts off [src] without even looking at what they're doing.</span>")
else

View File

@@ -160,6 +160,7 @@
/obj/item/weapon/melee/baton,
/obj/item/weapon/gun/energy/taser,
/obj/item/weapon/gun/energy/stunrevolver,
/obj/item/weapon/gun/magnetic/railgun/heater/pistol,
/obj/item/weapon/gun/energy/gun,
/obj/item/weapon/flame/lighter,
/obj/item/device/flashlight,

View File

@@ -142,15 +142,7 @@
name = "chameleon kit"
desc = "Comes with all the clothes you need to impersonate most people. Acting lessons sold seperately."
starts_with = list(
/obj/item/clothing/under/chameleon,
/obj/item/clothing/head/chameleon,
/obj/item/clothing/suit/chameleon,
/obj/item/clothing/shoes/chameleon,
/obj/item/weapon/storage/backpack/chameleon,
/obj/item/clothing/gloves/chameleon,
/obj/item/clothing/mask/chameleon,
/obj/item/clothing/glasses/chameleon,
/obj/item/clothing/accessory/chameleon,
/obj/item/weapon/storage/backpack/chameleon/full,
/obj/item/weapon/gun/energy/chameleon
)

View File

@@ -110,6 +110,7 @@
/obj/item/device/flash,
/obj/item/weapon/melee/baton/loaded,
/obj/item/weapon/gun/magnetic/railgun/heater/pistol/hos,
/obj/item/weapon/rcd_ammo/large,
/obj/item/weapon/cell/device/weapon,
/obj/item/clothing/accessory/holster/waist,
/obj/item/weapon/melee/telebaton,

View File

@@ -222,6 +222,44 @@
icon_state = "plant-01"
plane = OBJ_PLANE
var/obj/item/stored_item
/obj/structure/flora/pottedplant/examine(mob/user)
..()
if(in_range(user, src) && stored_item)
to_chat(user, "<i>You can see something in there...</i>")
/obj/structure/flora/pottedplant/attackby(obj/item/I, mob/user)
if(stored_item)
to_chat(user, "<span class='notice'>[I] won't fit in. There already appears to be something in here...</span>")
return
if(I.w_class > ITEMSIZE_TINY)
to_chat(user, "<span class='notice'>[I] is too big to fit inside [src].</span>")
return
if(do_after(user, 10))
user.drop_from_inventory(I, src)
I.forceMove(src)
stored_item = I
src.visible_message("\icon[src] \icon[I] [user] places [I] into [src].")
return
else
to_chat(user, "<span class='notice'>You refrain from putting things into the plant pot.</span>")
return
..()
/obj/structure/flora/pottedplant/attack_hand(mob/user)
if(!stored_item)
to_chat(user, "<b>You see nothing of interest in [src]...</b>")
else
if(do_after(user, 10))
to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
stored_item.forceMove(get_turf(src))
stored_item = null
..()
/obj/structure/flora/pottedplant/large
name = "large potted plant"

View File

@@ -0,0 +1,366 @@
/obj/structure/salvageable
name = "broken macninery"
desc = "Broken beyond repair, but looks like you can still salvage something from this if you had a prying implement."
icon = 'icons/obj/salvageable.dmi'
density = 1
anchored = 1
var/salvageable_parts = list()
/obj/structure/salvageable/proc/dismantle()
new /obj/structure/frame (src.loc)
for(var/path in salvageable_parts)
if(prob(salvageable_parts[path]))
new path (loc)
return
/obj/structure/salvageable/attackby(obj/item/I, mob/user)
if(I.is_crowbar())
playsound(loc, I.usesound, 50, 1)
var/actual_time = I.toolspeed * 170
user.visible_message( \
"<span class='notice'>\The [user] begins salvaging from \the [src].</span>", \
"<span class='notice'>You start salvaging from \the [src].</span>")
if(do_after(user, actual_time, target = src))
user.visible_message( \
"<span class='notice'>\The [user] has salvaged \the [src].</span>", \
"<span class='notice'>You salvage \the [src].</span>")
dismantle()
qdel(src)
return TRUE
return ..()
//Types themself, use them, but not the parent object
/obj/structure/salvageable/machine
name = "broken machine"
icon_state = "machine1"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 80,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/stock_parts/capacitor = 40,
/obj/item/weapon/stock_parts/capacitor = 40,
/obj/item/weapon/stock_parts/scanning_module = 40,
/obj/item/weapon/stock_parts/scanning_module = 40,
/obj/item/weapon/stock_parts/manipulator = 40,
/obj/item/weapon/stock_parts/manipulator = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/capacitor/adv = 20,
/obj/item/weapon/stock_parts/scanning_module/adv = 20,
/obj/item/weapon/stock_parts/manipulator/nano = 20,
/obj/item/weapon/stock_parts/micro_laser/high = 20,
/obj/item/weapon/stock_parts/matter_bin/adv = 20
)
/obj/structure/salvageable/machine/Initialize()
. = ..()
icon_state = "machine[rand(0,6)]"
/obj/structure/salvageable/computer
name = "broken computer"
icon_state = "computer0"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 90,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/computer_hardware/network_card = 40,
/obj/item/weapon/computer_hardware/network_card = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/stock_parts/capacitor/adv = 30,
/obj/item/weapon/computer_hardware/network_card/advanced = 20
)
obj/structure/salvageable/computer/Initialize()
. = ..()
icon_state = "computer[rand(0,7)]"
/obj/structure/salvageable/autolathe
name = "broken autolathe"
icon_state = "autolathe"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 80,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/stock_parts/capacitor = 40,
/obj/item/weapon/stock_parts/scanning_module = 40,
/obj/item/weapon/stock_parts/manipulator = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/capacitor/adv = 20,
/obj/item/weapon/stock_parts/micro_laser/high = 20,
/obj/item/weapon/stock_parts/micro_laser/high = 20,
/obj/item/weapon/stock_parts/matter_bin/adv = 20,
/obj/item/weapon/stock_parts/matter_bin/adv = 20,
/obj/item/stack/material/steel{amount = 20} = 40,
/obj/item/stack/material/glass{amount = 20} = 40,
/obj/item/stack/material/plastic{amount = 20} = 40,
/obj/item/stack/material/plasteel{amount = 10} = 40,
/obj/item/stack/material/silver{amount = 10} = 20,
/obj/item/stack/material/gold{amount = 10} = 20,
/obj/item/stack/material/phoron{amount = 10} = 20
)
/obj/structure/salvageable/implant_container
name = "old container"
icon_state = "implant_container0"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 80,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/implant/death_alarm = 15,
/obj/item/weapon/implant/explosive = 10,
/obj/item/weapon/implant/freedom = 5,
/obj/item/weapon/implant/tracking = 10,
/obj/item/weapon/implant/chem = 10,
/obj/item/weapon/implantcase = 30,
/obj/item/weapon/implanter = 30,
/obj/item/stack/material/steel{amount = 10} = 30,
/obj/item/stack/material/glass{amount = 10} = 30,
/obj/item/stack/material/silver{amount = 10} = 30
)
obj/structure/salvageable/implant_container/Initialize()
. = ..()
icon_state = "implant_container[rand(0,1)]"
/obj/structure/salvageable/data
name = "broken data storage"
icon_state = "data0"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 90,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/computer_hardware/network_card = 40,
/obj/item/weapon/computer_hardware/network_card = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/hard_drive = 50,
/obj/item/weapon/computer_hardware/hard_drive = 50,
/obj/item/weapon/computer_hardware/hard_drive = 50,
/obj/item/weapon/computer_hardware/hard_drive = 50,
/obj/item/weapon/computer_hardware/hard_drive = 50,
/obj/item/weapon/computer_hardware/hard_drive = 50,
/obj/item/weapon/computer_hardware/hard_drive/advanced = 30,
/obj/item/weapon/computer_hardware/hard_drive/advanced = 30,
/obj/item/weapon/computer_hardware/network_card/advanced = 20
)
obj/structure/salvageable/data/Initialize()
. = ..()
icon_state = "data[rand(0,1)]"
/obj/structure/salvageable/server
name = "broken server"
icon_state = "server0"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 90,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/computer_hardware/network_card = 40,
/obj/item/weapon/computer_hardware/network_card = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/stock_parts/subspace/amplifier = 40,
/obj/item/weapon/stock_parts/subspace/amplifier = 40,
/obj/item/weapon/stock_parts/subspace/analyzer = 40,
/obj/item/weapon/stock_parts/subspace/analyzer = 40,
/obj/item/weapon/stock_parts/subspace/ansible = 40,
/obj/item/weapon/stock_parts/subspace/ansible = 40,
/obj/item/weapon/stock_parts/subspace/transmitter = 40,
/obj/item/weapon/stock_parts/subspace/transmitter = 40,
/obj/item/weapon/stock_parts/subspace/crystal = 30,
/obj/item/weapon/stock_parts/subspace/crystal = 30,
/obj/item/weapon/computer_hardware/network_card/advanced = 20
)
obj/structure/salvageable/server/Initialize()
. = ..()
icon_state = "server[rand(0,1)]"
/obj/structure/salvageable/personal
name = "personal terminal"
icon_state = "personal0"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 90,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 70,
/obj/item/trash/material/circuit = 60,
/obj/item/trash/material/metal = 60,
/obj/item/weapon/computer_hardware/network_card = 60,
/obj/item/weapon/computer_hardware/network_card/advanced = 40,
/obj/item/weapon/computer_hardware/network_card/wired = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/processor_unit = 60,
/obj/item/weapon/computer_hardware/processor_unit/small = 50,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
/obj/item/weapon/computer_hardware/processor_unit/photonic/small = 30,
/obj/item/weapon/computer_hardware/hard_drive = 60,
/obj/item/weapon/computer_hardware/hard_drive/advanced = 40
)
obj/structure/salvageable/personal/Initialize()
. = ..()
icon_state = "personal[rand(0,12)]"
new /obj/structure/table/reinforced (loc)
/obj/structure/salvageable/bliss
name = "strange terminal"
icon_state = "bliss0"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 90,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 60,
/obj/item/weapon/computer_hardware/hard_drive/cluster = 50
)
obj/structure/salvageable/bliss/Initialize()
. = ..()
icon_state = "bliss[rand(0,1)]"
/obj/structure/salvageable/bliss/attackby(obj/item/I, mob/user)
if((. = ..()))
playsound(user, 'sound/machines/shutdown.ogg', 60, 1)
//////////////////
//// ONE STAR ////
//////////////////
/obj/structure/salvageable/machine_os
name = "broken machine"
icon_state = "os-machine"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 80,
/obj/item/weapon/stock_parts/capacitor = 40,
/obj/item/weapon/stock_parts/capacitor = 40,
/obj/item/weapon/stock_parts/scanning_module = 40,
/obj/item/weapon/stock_parts/scanning_module = 40,
/obj/item/weapon/stock_parts/manipulator = 40,
/obj/item/weapon/stock_parts/manipulator = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/micro_laser = 40,
/obj/item/weapon/stock_parts/matter_bin = 40,
/obj/item/weapon/stock_parts/matter_bin = 40
)
/obj/structure/salvageable/computer_os
name = "broken computer"
icon_state = "os-computer"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 90,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/network_card/advanced = 40
)
/obj/structure/salvageable/implant_container_os
name = "old container"
icon_state = "os-container"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 80,
/obj/item/weapon/implant/death_alarm = 30,
/obj/item/weapon/implant/explosive = 20,
/obj/item/weapon/implant/freedom = 20,
/obj/item/weapon/implant/tracking = 30,
/obj/item/weapon/implant/chem = 30,
/obj/item/weapon/implantcase = 30,
/obj/item/weapon/implanter = 30
)
/obj/structure/salvageable/data_os
name = "broken data storage"
icon_state = "os-data"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 90,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 90,
/obj/item/weapon/computer_hardware/processor_unit/small = 60,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 50,
/obj/item/weapon/computer_hardware/hard_drive/super = 50,
/obj/item/weapon/computer_hardware/hard_drive/super = 50,
/obj/item/weapon/computer_hardware/hard_drive/cluster = 50,
/obj/item/weapon/computer_hardware/network_card/wired = 40
)
/obj/structure/salvageable/server_os
name = "broken server"
icon_state = "os-server"
salvageable_parts = list(
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/stack/material/glass{amount = 5} = 90,
/obj/item/weapon/computer_hardware/network_card/wired = 40,
/obj/item/weapon/computer_hardware/network_card/wired = 40,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
/obj/item/weapon/stock_parts/subspace/amplifier = 40,
/obj/item/weapon/stock_parts/subspace/amplifier = 40,
/obj/item/weapon/stock_parts/subspace/analyzer = 40,
/obj/item/weapon/stock_parts/subspace/analyzer = 40,
/obj/item/weapon/stock_parts/subspace/ansible = 40,
/obj/item/weapon/stock_parts/subspace/ansible = 40,
/obj/item/weapon/stock_parts/subspace/transmitter = 40,
/obj/item/weapon/stock_parts/subspace/transmitter = 40,
/obj/item/weapon/stock_parts/subspace/crystal = 30,
/obj/item/weapon/stock_parts/subspace/crystal = 30,
/obj/item/weapon/computer_hardware/network_card/wired = 20
)
/obj/structure/salvageable/console_os
name = "pristine console"
desc = "Despite being in pristine condition this console doesn't respond to anything, but looks like you can still salvage something from this."
icon_state = "os_console"
salvageable_parts = list(
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/computer_hardware/processor_unit/small = 40,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/network_card/advanced = 40
)
/obj/structure/salvageable/console_broken_os
name = "broken console"
icon_state = "os_console_broken"
salvageable_parts = list(
/obj/item/stack/cable_coil{amount = 5} = 90,
/obj/item/weapon/stock_parts/console_screen = 80,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/stock_parts/capacitor = 60,
/obj/item/weapon/computer_hardware/processor_unit = 40,
/obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/card_slot = 40,
/obj/item/weapon/computer_hardware/network_card/advanced = 40
)

View File

@@ -124,6 +124,12 @@
/obj/structure/bed/chair/comfy/lime/New(var/newloc,var/newmaterial)
..(newloc,"steel","lime")
/obj/structure/bed/chair/comfy/yellow/New(var/newloc,var/newmaterial)
..(newloc,"steel","yellow")
/obj/structure/bed/chair/comfy/orange/New(var/newloc,var/newmaterial)
..(newloc,"steel","orange")
/obj/structure/bed/chair/office
anchored = 0
buckle_movable = 1
@@ -219,8 +225,8 @@
/obj/structure/bed/chair/sofa/update_icon()
if(applies_material_colour && sofa_material)
material = get_material_by_name(sofa_material)
color = material.icon_colour
var/material/color_material = get_material_by_name(sofa_material)
color = color_material.icon_colour
if(sofa_material == "carpet")
name = "red [initial(name)]"
@@ -271,6 +277,9 @@
/obj/structure/bed/chair/sofa/yellow
sofa_material = "yellow"
/obj/structure/bed/chair/sofa/orange
sofa_material = "orange"
//sofa directions
/obj/structure/bed/chair/sofa/left
@@ -362,3 +371,12 @@
/obj/structure/bed/chair/sofa/yellow/corner
icon_state = "sofacorner"
/obj/structure/bed/chair/sofa/orange/left
icon_state = "sofaend_left"
/obj/structure/bed/chair/sofa/orange/right
icon_state = "sofaend_right"
/obj/structure/bed/chair/sofa/orange/corner
icon_state = "sofacorner"

View File

@@ -161,3 +161,17 @@ var/list/shoreline_icon_cache = list()
if(L.get_water_protection() < 1)
return FALSE
return ..()
/turf/simulated/floor/water/contaminated
desc = "This water smells pretty acrid."
var poisonlevel = 10
turf/simulated/floor/water/contaminated/Entered(atom/movable/AM, atom/oldloc)
..()
if(istype(AM, /mob/living))
var/mob/living/L = AM
if(L.isSynthetic())
return
poisonlevel *= 1 - L.get_water_protection()
if(poisonlevel > 0)
L.adjustToxLoss(poisonlevel)

View File

@@ -148,14 +148,14 @@ var/world_topic_spam_protect_time = world.timeofday
else if(T == "manifest")
var/list/positions = list()
var/list/set_names = list(
"heads" = command_positions,
"sec" = security_positions,
"eng" = engineering_positions,
"med" = medical_positions,
"sci" = science_positions,
"car" = cargo_positions,
"civ" = civilian_positions,
"bot" = nonhuman_positions
"heads" = SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND),
"sec" = SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY),
"eng" = SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING),
"med" = SSjob.get_job_titles_in_department(DEPARTMENT_MEDICAL),
"sci" = SSjob.get_job_titles_in_department(DEPARTMENT_RESEARCH),
"car" = SSjob.get_job_titles_in_department(DEPARTMENT_CARGO),
"civ" = SSjob.get_job_titles_in_department(DEPARTMENT_CIVILIAN),
"bot" = SSjob.get_job_titles_in_department(DEPARTMENT_SYNTHETIC)
)
for(var/datum/data/record/t in data_core.general)

View File

@@ -178,6 +178,7 @@ var/list/station_departments = list("Command", "Medical", "Engineering", "Scienc
//Icons for in-game HUD glasses. Why don't we just share these a little bit?
var/static/icon/ingame_hud = icon('icons/mob/hud.dmi')
var/static/icon/ingame_hud_med = icon('icons/mob/hud_med.dmi')
var/static/icon/buildmode_hud = icon('icons/misc/buildmode.dmi')
//Keyed list for caching icons so you don't need to make them for records, IDs, etc all separately.
//Could be useful for AI impersonation or something at some point?

View File

@@ -300,7 +300,7 @@ datum/admins/proc/DB_ban_unban_by_id(var/id)
output += "<option value=''>--</option>"
for(var/j in get_all_jobs())
output += "<option value='[j]'>[j]</option>"
for(var/j in nonhuman_positions)
for(var/j in SSjob.get_job_titles_in_department(DEPARTMENT_SYNTHETIC))
output += "<option value='[j]'>[j]</option>"
var/list/bantypes = list("traitor","changeling","operative","revolutionary","cultist","wizard") //For legacy bans.
for(var/antag_type in all_antag_types) // Grab other bans.

View File

@@ -105,7 +105,8 @@ var/list/admin_verbs_admin = list(
/datum/admins/proc/paralyze_mob,
/client/proc/fixatmos,
/datum/admins/proc/sendFax,
/client/proc/despawn_player
/client/proc/despawn_player,
/datum/admins/proc/view_feedback
)
var/list/admin_verbs_ban = list(
@@ -228,7 +229,8 @@ var/list/admin_verbs_debug = list(
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
/client/proc/admin_give_modifier,
/client/proc/simple_DPS
/client/proc/simple_DPS,
/datum/admins/proc/view_feedback
)
var/list/admin_verbs_paranoid_debug = list(

View File

@@ -394,8 +394,8 @@
//Regular jobs
//Command (Blue)
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr align='center' bgcolor='ccccff'><th colspan='[length(command_positions)]'><a href='?src=\ref[src];jobban3=commanddept;jobban4=\ref[M]'>Command Positions</a></th></tr><tr align='center'>"
for(var/jobPos in command_positions)
jobs += "<tr align='center' bgcolor='ccccff'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND))]'><a href='?src=\ref[src];jobban3=commanddept;jobban4=\ref[M]'>Command Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -415,8 +415,8 @@
//Security (Red)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='ffddf0'><th colspan='[length(security_positions)]'><a href='?src=\ref[src];jobban3=securitydept;jobban4=\ref[M]'>Security Positions</a></th></tr><tr align='center'>"
for(var/jobPos in security_positions)
jobs += "<tr bgcolor='ffddf0'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY))]'><a href='?src=\ref[src];jobban3=securitydept;jobban4=\ref[M]'>Security Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -436,8 +436,8 @@
//Engineering (Yellow)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='fff5cc'><th colspan='[length(engineering_positions)]'><a href='?src=\ref[src];jobban3=engineeringdept;jobban4=\ref[M]'>Engineering Positions</a></th></tr><tr align='center'>"
for(var/jobPos in engineering_positions)
jobs += "<tr bgcolor='fff5cc'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING))]'><a href='?src=\ref[src];jobban3=engineeringdept;jobban4=\ref[M]'>Engineering Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -457,8 +457,8 @@
//Cargo (Yellow)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='fff5cc'><th colspan='[length(cargo_positions)]'><a href='?src=\ref[src];jobban3=cargodept;jobban4=\ref[M]'>Cargo Positions</a></th></tr><tr align='center'>"
for(var/jobPos in cargo_positions)
jobs += "<tr bgcolor='fff5cc'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_CARGO))]'><a href='?src=\ref[src];jobban3=cargodept;jobban4=\ref[M]'>Cargo Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_CARGO))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -478,8 +478,8 @@
//Medical (White)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='ffeef0'><th colspan='[length(medical_positions)]'><a href='?src=\ref[src];jobban3=medicaldept;jobban4=\ref[M]'>Medical Positions</a></th></tr><tr align='center'>"
for(var/jobPos in medical_positions)
jobs += "<tr bgcolor='ffeef0'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_MEDICAL))]'><a href='?src=\ref[src];jobban3=medicaldept;jobban4=\ref[M]'>Medical Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_MEDICAL))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -499,8 +499,8 @@
//Science (Purple)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='e79fff'><th colspan='[length(science_positions)]'><a href='?src=\ref[src];jobban3=sciencedept;jobban4=\ref[M]'>Science Positions</a></th></tr><tr align='center'>"
for(var/jobPos in science_positions)
jobs += "<tr bgcolor='e79fff'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_RESEARCH))]'><a href='?src=\ref[src];jobban3=sciencedept;jobban4=\ref[M]'>Science Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_RESEARCH))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -520,8 +520,8 @@
//Civilian (Grey)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='dddddd'><th colspan='[length(civilian_positions)]'><a href='?src=\ref[src];jobban3=civiliandept;jobban4=\ref[M]'>Civilian Positions</a></th></tr><tr align='center'>"
for(var/jobPos in civilian_positions)
jobs += "<tr bgcolor='dddddd'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_CIVILIAN))]'><a href='?src=\ref[src];jobban3=civiliandept;jobban4=\ref[M]'>Civilian Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_CIVILIAN))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -547,8 +547,8 @@
//Non-Human (Green)
counter = 0
jobs += "<table cellpadding='1' cellspacing='0' width='100%'>"
jobs += "<tr bgcolor='ccffcc'><th colspan='[length(nonhuman_positions)+1]'><a href='?src=\ref[src];jobban3=nonhumandept;jobban4=\ref[M]'>Non-human Positions</a></th></tr><tr align='center'>"
for(var/jobPos in nonhuman_positions)
jobs += "<tr bgcolor='ccffcc'><th colspan='[length(SSjob.get_job_titles_in_department(DEPARTMENT_SYNTHETIC))+1]'><a href='?src=\ref[src];jobban3=nonhumandept;jobban4=\ref[M]'>Non-human Positions</a></th></tr><tr align='center'>"
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_SYNTHETIC))
if(!jobPos) continue
var/datum/job/job = job_master.GetJob(jobPos)
if(!job) continue
@@ -635,50 +635,50 @@
var/list/joblist = list()
switch(href_list["jobban3"])
if("commanddept")
for(var/jobPos in command_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("securitydept")
for(var/jobPos in security_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("engineeringdept")
for(var/jobPos in engineering_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("cargodept")
for(var/jobPos in cargo_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_CARGO))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("medicaldept")
for(var/jobPos in medical_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_MEDICAL))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("sciencedept")
for(var/jobPos in science_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_RESEARCH))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("civiliandept")
for(var/jobPos in civilian_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_CIVILIAN))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue
joblist += temp.title
if("nonhumandept")
joblist += "pAI"
for(var/jobPos in nonhuman_positions)
for(var/jobPos in SSjob.get_job_titles_in_department(DEPARTMENT_SYNTHETIC))
if(!jobPos) continue
var/datum/job/temp = job_master.GetJob(jobPos)
if(!temp) continue

View File

@@ -1,3 +1,15 @@
#define BUILDMODE_BASIC 1
#define BUILDMODE_ADVANCED 2
#define BUILDMODE_EDIT 3
#define BUILDMODE_THROW 4
#define BUILDMODE_ROOM 5
#define BUILDMODE_LADDER 6
#define BUILDMODE_CONTENTS 7
#define BUILDMODE_LIGHTS 8
#define BUILDMODE_AI 9
#define LAST_BUILDMODE 9
/proc/togglebuildmode(mob/M as mob in player_list)
set name = "Toggle Build Mode"
set category = "Special Verbs"
@@ -6,6 +18,7 @@
log_admin("[key_name(usr)] has left build mode.")
M.client.buildmode = 0
M.client.show_popup_menus = 1
M.plane_holder.set_vis(VIS_BUILDMODE, FALSE)
for(var/obj/effect/bmode/buildholder/H)
if(H.cl == M.client)
qdel(H)
@@ -13,6 +26,7 @@
log_admin("[key_name(usr)] has entered build mode.")
M.client.buildmode = 1
M.client.show_popup_menus = 0
M.plane_holder.set_vis(VIS_BUILDMODE, TRUE)
var/obj/effect/bmode/buildholder/H = new/obj/effect/bmode/buildholder()
var/obj/effect/bmode/builddir/A = new/obj/effect/bmode/builddir(H)
@@ -72,7 +86,8 @@
screen_loc = "NORTH,WEST+1"
Click()
switch(master.cl.buildmode)
if(1) // Basic Build
if(BUILDMODE_BASIC)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button = Construct / Upgrade</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button = Deconstruct / Delete / Downgrade</span>")
@@ -82,7 +97,8 @@
to_chat(usr, "<span class='notice'>Use the button in the upper left corner to</span>")
to_chat(usr, "<span class='notice'>change the direction of built objects.</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(2) // Adv. Build
if(BUILDMODE_ADVANCED)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on buildmode button = Set object type</span>")
to_chat(usr, "<span class='notice'>Middle Mouse Button on buildmode button= On/Off object type saying</span>")
@@ -94,44 +110,56 @@
to_chat(usr, "<span class='notice'>Use the button in the upper left corner to</span>")
to_chat(usr, "<span class='notice'>change the direction of built objects.</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(3) // Edit
if(BUILDMODE_EDIT)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on buildmode button = Select var(type) & value</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on turf/obj/mob = Set var(type) & value</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on turf/obj/mob = Reset var's value</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(4) // Throw
if(BUILDMODE_THROW)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on turf/obj/mob = Select</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on turf/obj/mob = Throw</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(5) // Room Build
if(BUILDMODE_ROOM)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on turf = Select as point A</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on turf = Select as point B</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on buildmode button = Change floor/wall type</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(6) // Make Ladders
if(BUILDMODE_LADDER)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on turf = Set as upper ladder loc</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on turf = Set as lower ladder loc</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(7) // Move Into Contents
if(BUILDMODE_CONTENTS)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on turf/obj/mob = Select</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on turf/obj/mob = Move into selection</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(8) // Make Lights
if(BUILDMODE_LIGHTS)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on turf/obj/mob = Make it glow</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on turf/obj/mob = Reset glowing</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on buildmode button = Change glow properties</span>")
to_chat(usr, "<span class='notice'>***********************************************************</span>")
if(9) // Control mobs with ai_holders.
if(BUILDMODE_AI)
to_chat(usr, "<span class='notice'>***********************************************************</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button drag box = Select only mobs in box</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button drag box + shift = Select additional mobs in area</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on non-mob = Deselect all mobs</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button on AI mob = Select/Deselect mob</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button + alt on AI mob = Toggle hostility on mob</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button + ctrl on AI mob = Reset target/following/movement</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button + shift on AI mob = Toggle AI (also resets)</span>")
to_chat(usr, "<span class='notice'>Left Mouse Button + ctrl on AI mob = Copy mob faction</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button + ctrl on any mob = Paste mob faction copied with Left Mouse Button + shift</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on enemy mob = Command selected mobs to attack mob</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button on allied mob = Command selected mobs to follow mob</span>")
to_chat(usr, "<span class='notice'>Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction</span>")
@@ -159,6 +187,7 @@
var/obj/effect/bmode/buildquit/buildquit = null
var/atom/movable/throw_atom = null
var/list/selected_mobs = list()
var/copied_faction = null
/obj/effect/bmode/buildholder/Destroy()
qdel(builddir)
@@ -184,7 +213,6 @@
selected_mobs -= unit
C.images -= unit.selected_image
/obj/effect/bmode/buildmode
icon_state = "buildmode1"
screen_loc = "NORTH,WEST+2"
@@ -207,48 +235,25 @@
if(pa.Find("middle"))
switch(master.cl.buildmode)
if(2)
if(BUILDMODE_ADVANCED)
objsay=!objsay
if(pa.Find("left"))
switch(master.cl.buildmode)
if(1)
master.cl.buildmode = 2
src.icon_state = "buildmode2"
if(2)
master.cl.buildmode = 3
src.icon_state = "buildmode3"
if(3)
master.cl.buildmode = 4
src.icon_state = "buildmode4"
if(4)
master.cl.buildmode = 5
src.icon_state = "buildmode5"
if(5)
master.cl.buildmode = 6
src.icon_state = "buildmode6"
if(6)
master.cl.buildmode = 7
src.icon_state = "buildmode7"
if(7)
master.cl.buildmode = 8
src.icon_state = "buildmode8"
if(8)
master.cl.buildmode = 9
src.icon_state = "buildmode9"
if(9)
if(master.cl.buildmode == LAST_BUILDMODE)
master.cl.buildmode = 1
src.icon_state = "buildmode1"
else
master.cl.buildmode++
src.icon_state = "buildmode[master.cl.buildmode]"
else if(pa.Find("right"))
switch(master.cl.buildmode)
if(1) // Basic Build
if(BUILDMODE_BASIC)
return 1
if(2) // Adv. Build
if(BUILDMODE_ADVANCED)
objholder = get_path_from_partial_text(/obj/structure/closet)
if(3) // Edit
if(BUILDMODE_EDIT)
var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine")
master.buildmode.varholder = input(usr,"Enter variable name:" ,"Name", "name")
@@ -267,14 +272,16 @@
master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as obj in world
if("turf-reference")
master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as turf in world
if(5) // Room build
if(BUILDMODE_ROOM)
var/choice = alert("Would you like to change the floor or wall holders?","Room Builder", "Floor", "Wall")
switch(choice)
if("Floor")
floor_holder = get_path_from_partial_text(/turf/simulated/floor/plating)
if("Wall")
wall_holder = get_path_from_partial_text(/turf/simulated/wall)
if(8) // Lights
if(BUILDMODE_LIGHTS)
var/choice = alert("Change the new light range, power, or color?", "Light Maker", "Range", "Power", "Color")
switch(choice)
if("Range")
@@ -301,7 +308,7 @@
var/list/pa = params2list(params)
switch(buildmode)
if(1) // Basic Build
if(BUILDMODE_BASIC)
if(istype(object,/turf) && pa.Find("left") && !pa.Find("alt") && !pa.Find("ctrl") )
if(istype(object,/turf/space))
var/turf/T = object
@@ -350,7 +357,8 @@
if(NORTHWEST)
var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
WIN.set_dir(NORTHWEST)
if(2) // Adv. Build
if(BUILDMODE_ADVANCED)
if(pa.Find("left") && !pa.Find("ctrl"))
if(ispath(holder.buildmode.objholder,/turf))
var/turf/T = get_turf(object)
@@ -369,8 +377,7 @@
if(holder.buildmode.objsay)
to_chat(usr, "[object.type]")
if(3) // Edit
if(BUILDMODE_EDIT)
if(pa.Find("left")) //I cant believe this shit actually compiles.
if(object.vars.Find(holder.buildmode.varholder))
log_admin("[key_name(usr)] modified [object.name]'s [holder.buildmode.varholder] to [holder.buildmode.valueholder]")
@@ -384,7 +391,7 @@
else
to_chat(user, "<span class='danger'>[initial(object.name)] does not have a var called '[holder.buildmode.varholder]'</span>")
if(4) // Throw
if(BUILDMODE_THROW)
if(pa.Find("left"))
if(istype(object, /atom/movable))
holder.throw_atom = object
@@ -392,7 +399,8 @@
if(holder.throw_atom)
holder.throw_atom.throw_at(object, 10, 1)
log_admin("[key_name(usr)] threw [holder.throw_atom] at [object]")
if(5) // Room build
if(BUILDMODE_ROOM)
if(pa.Find("left"))
holder.buildmode.coordA = get_turf(object)
to_chat(user, "<span class='notice'>Defined [object] ([object.type]) as point A.</span>")
@@ -411,7 +419,8 @@
)
holder.buildmode.coordA = null
holder.buildmode.coordB = null
if(6) // Ladders
if(BUILDMODE_LADDER)
if(pa.Find("left"))
holder.buildmode.coordA = get_turf(object)
to_chat(user, "<span class='notice'>Defined [object] ([object.type]) as upper ladder location.</span>")
@@ -430,7 +439,8 @@
B.update_icon()
holder.buildmode.coordA = null
holder.buildmode.coordB = null
if(7) // Move into contents
if(BUILDMODE_CONTENTS)
if(pa.Find("left"))
if(istype(object, /atom))
holder.throw_atom = object
@@ -438,23 +448,32 @@
if(holder.throw_atom && istype(object, /atom/movable))
object.forceMove(holder.throw_atom)
log_admin("[key_name(usr)] moved [object] into [holder.throw_atom].")
if(8) // Lights
if(BUILDMODE_LIGHTS)
if(pa.Find("left"))
if(object)
object.set_light(holder.buildmode.new_light_range, holder.buildmode.new_light_intensity, holder.buildmode.new_light_color)
if(pa.Find("right"))
if(object)
object.set_light(0, 0, "#FFFFFF")
if(9) // AI control
if(BUILDMODE_AI)
if(pa.Find("left"))
if(isliving(object))
var/mob/living/L = object
// Reset processes.
if(pa.Find("ctrl"))
if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on.
// Pause/unpause AI
if(pa.Find("shift"))
var/stance = L.get_AI_stance()
if(!isnull(stance)) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on.
var/datum/ai_holder/AI = L.ai_holder
AI.forget_everything()
to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader."))
if(stance == STANCE_SLEEP)
AI.go_wake()
to_chat(user, span("notice", "\The [L]'s AI has been enabled."))
else
AI.go_sleep()
to_chat(user, span("notice", "\The [L]'s AI has been disabled."))
return
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
@@ -469,6 +488,12 @@
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
// Copy faction
if(pa.Find("ctrl"))
holder.copied_faction = L.faction
to_chat(user, span("notice", "Copied faction '[holder.copied_faction]'."))
return
// Select/Deselect
if(!isnull(L.get_AI_stance()))
if(L in holder.selected_mobs)
@@ -477,10 +502,27 @@
else
holder.select_AI_mob(user.client, L)
to_chat(user, span("notice", "Selected \the [L]."))
return
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
else //Not living
for(var/mob/living/unit in holder.selected_mobs)
holder.deselect_AI_mob(user.client, unit)
if(pa.Find("right"))
// Paste faction
if(pa.Find("ctrl") && isliving(object))
if(!holder.copied_faction)
to_chat(user, span("warning", "LMB+Shift a mob to copy their faction before pasting."))
return
else
var/mob/living/L = object
L.faction = holder.copied_faction
to_chat(user, span("notice", "Pasted faction '[holder.copied_faction]'."))
return
if(istype(object, /atom)) // Force attack.
var/atom/A = object
@@ -491,6 +533,9 @@
AI.give_target(A)
i++
to_chat(user, span("notice", "Commanded [i] mob\s to attack \the [A]."))
var/image/orderimage = image(buildmode_hud,A,"ai_targetorder")
orderimage.plane = PLANE_BUILDMODE
flick_overlay(orderimage, list(user.client), 8, TRUE)
return
if(isliving(object)) // Follow or attack.
@@ -515,16 +560,67 @@
if(j)
message += "[j] mob\s to follow \the [L]."
to_chat(user, span("notice", message))
var/image/orderimage = image(buildmode_hud,L,"ai_targetorder")
orderimage.plane = PLANE_BUILDMODE
flick_overlay(orderimage, list(user.client), 8, TRUE)
return
if(isturf(object)) // Move or reposition.
var/turf/T = object
var/i = 0
var/forced = 0
var/told = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
if(unit.get_AI_stance() == STANCE_SLEEP)
unit.forceMove(T)
forced++
else
AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy.
i++
to_chat(user, span("notice", "Commanded [i] mob\s to move to \the [T]."))
told++
to_chat(user, span("notice", "Commanded [told] mob\s to move to \the [T], and manually placed [forced] of them."))
var/image/orderimage = image(buildmode_hud,T,"ai_turforder")
orderimage.plane = PLANE_BUILDMODE
flick_overlay(orderimage, list(user.client), 8, TRUE)
return
/proc/build_drag(var/client/user, buildmode, var/atom/fromatom, var/atom/toatom, var/atom/fromloc, var/atom/toloc, var/fromcontrol, var/tocontrol, params)
var/obj/effect/bmode/buildholder/holder = null
for(var/obj/effect/bmode/buildholder/H)
if(H.cl == user)
holder = H
break
if(!holder) return
var/list/pa = params2list(params)
switch(buildmode)
if(BUILDMODE_AI)
//Holding shift prevents the deselection of existing
if(!pa.Find("shift"))
for(var/mob/living/unit in holder.selected_mobs)
holder.deselect_AI_mob(user, unit)
var/turf/c1 = get_turf(fromatom)
var/turf/c2 = get_turf(toatom)
if(!c1 || !c2)
return //Dragged outside window or something
var/low_x = min(c1.x,c2.x)
var/low_y = min(c1.y,c2.y)
var/hi_x = max(c1.x,c2.x)
var/hi_y = max(c1.y,c2.y)
var/z = c1.z //Eh
var/i = 0
for(var/mob/living/L in living_mob_list)
if(L.z != z || L.client)
continue
if(L.x >= low_x && L.x <= hi_x && L.y >= low_y && L.y <= hi_y)
holder.select_AI_mob(user, L)
i++
to_chat(user, span("notice", "Band-selected [i] mobs."))
return
/obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path)
var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]")
@@ -597,3 +693,14 @@
T.ChangeTurf(floor_type)
else
new floor_type(T)
#undef BUILDMODE_BASIC
#undef BUILDMODE_ADVANCED
#undef BUILDMODE_EDIT
#undef BUILDMODE_THROW
#undef BUILDMODE_ROOM
#undef BUILDMODE_LADDER
#undef BUILDMODE_CONTENTS
#undef BUILDMODE_LIGHTS
#undef BUILDMODE_AI
#undef LAST_BUILDMODE

View File

@@ -638,6 +638,7 @@
return
var/datum/planet/planet = input(usr, "Which planet do you want to modify the weather on?", "Change Weather") in SSplanets.planets
if(istype(planet))
var/datum/weather/new_weather = input(usr, "What weather do you want to change to?", "Change Weather") as null|anything in planet.weather_holder.allowed_weather_types
if(new_weather)
planet.weather_holder.change_weather(new_weather)
@@ -655,7 +656,7 @@
return
var/datum/planet/planet = input(usr, "Which planet do you want to modify time on?", "Change Time") in SSplanets.planets
if(istype(planet))
var/datum/time/current_time_datum = planet.current_time
var/new_hour = input(usr, "What hour do you want to change to?", "Change Time", text2num(current_time_datum.show_time("hh"))) as null|num
if(!isnull(new_hour))

View File

@@ -1,6 +1,6 @@
/client/proc/admin_lightning_strike()
set name = "Lightning Strike"
set desc = "Causes lightning to strike on your tile. This will hurt things on or nearby it severely."
set desc = "Causes lightning to strike on your tile. This can be made to hurt things on or nearby it severely."
set category = "Fun"
if(!check_rights(R_FUN))
@@ -28,7 +28,7 @@
// Do a lightning flash for the whole planet, if the turf belongs to a planet.
var/datum/planet/P = null
P = SSplanets.z_to_planet[T.z]
P = LAZYACCESS(SSplanets.z_to_planet, T.z)
if(P)
var/datum/weather_holder/holder = P.weather_holder
flick("lightning_flash", holder.special_visuals)
@@ -64,7 +64,7 @@
// Otherwise only those on the current z-level will hear it.
var/sound = get_sfx("thunder")
for(var/mob/M in player_list)
if((P && M.z in P.expected_z_levels) || M.z == T.z)
if( (P && (M.z in P.expected_z_levels)) || M.z == T.z)
if(M.is_preference_enabled(/datum/client_preference/weather_sounds))
M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE)

View File

@@ -149,3 +149,40 @@
// Simple mobs that retaliate and support others in their faction who get attacked.
/datum/ai_holder/simple_mob/retaliate/cooperative
cooperative = TRUE
// With all the bells and whistles
/datum/ai_holder/simple_mob/humanoid
intelligence_level = AI_SMART //Purportedly
retaliate = TRUE //If attacked, attack back
threaten = TRUE //Verbal threats
firing_lanes = TRUE //Avoid shooting allies
conserve_ammo = TRUE //Don't shoot when it can't hit target
can_breakthrough = TRUE //Can break through doors
violent_breakthrough = FALSE //Won't try to break through walls (humans can, but usually don't)
speak_chance = 2 //Babble chance
cooperative = TRUE //Assist each other
wander = TRUE //Wander around
returns_home = TRUE //But not too far
use_astar = TRUE //Path smartly
home_low_priority = TRUE //Following/helping is more important
// The hostile subtype is implied to be trained combatants who use ""tactics""
/datum/ai_holder/simple_mob/humanoid/hostile
var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away.
hostile = TRUE //Attack!
// Juke
/datum/ai_holder/simple_mob/humanoid/hostile/post_melee_attack(atom/A)
holder.IMove(get_step(holder, pick(alldirs)))
holder.face_atom(A)
/datum/ai_holder/simple_mob/humanoid/hostile/post_ranged_attack(atom/A)
//Pick a random turf to step into
var/turf/T = get_step(holder, pick(alldirs))
if(check_trajectory(A, T)) // Can we even hit them from there?
holder.IMove(T)
holder.face_atom(A)
if(get_dist(holder, A) < run_if_this_close)
holder.IMove(get_step_away(holder, A))
holder.face_atom(A)

View File

@@ -77,7 +77,7 @@
if(rabid)
return
var/justified = my_slime.is_justified_to_discipline() // This will also consider the AI-side of that proc.
lost_target() // Stop attacking.
remove_target() // Stop attacking.
if(justified)
obedience++
@@ -137,7 +137,7 @@
// Called when using a pacification agent (or it's Kendrick being initalized).
/datum/ai_holder/simple_mob/xenobio_slime/proc/pacify()
lost_target() // So it stops trying to kill them.
remove_target() // So it stops trying to kill them.
rabid = FALSE
hostile = FALSE
retaliate = FALSE
@@ -167,7 +167,7 @@
return 1 // Melee (eat) the target if dead/dying, don't shoot it.
return ..()
/datum/ai_holder/simple_mob/xenobio_slime/can_attack(atom/movable/AM)
/datum/ai_holder/simple_mob/xenobio_slime/can_attack(atom/movable/AM, var/vision_required = TRUE)
. = ..()
if(.) // Do some additional checks because we have Special Code(tm).
if(ishuman(AM))
@@ -247,7 +247,7 @@
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified.
lost_target()
remove_target()
if(leader) // We're being asked to stop following someone.

View File

@@ -46,8 +46,25 @@
home_turf = null
return ..()
/datum/ai_holder/proc/update_stance_hud()
var/image/stanceimage = holder.grab_hud(LIFE_HUD)
stanceimage.icon_state = "ais_[stance]"
holder.apply_hud(LIFE_HUD, stanceimage)
/datum/ai_holder/proc/update_paused_hud()
var/image/sleepingimage = holder.grab_hud(STATUS_HUD)
var/asleep = 0
if(busy)
asleep = 2
else if (stance == STANCE_SLEEP)
asleep = 1
sleepingimage.icon_state = "ai_[asleep]"
holder.apply_hud(STATUS_HUD, sleepingimage)
// Now for the actual AI stuff.
/datum/ai_holder/proc/set_busy(var/value = 0)
busy = value
update_paused_hud()
// Makes this ai holder not get processed.
// Called automatically when the host mob is killed.
@@ -58,6 +75,7 @@
forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use.
set_stance(STANCE_SLEEP)
SSai.processing -= src
update_paused_hud()
// Reverses the above proc.
// Revived mobs will wake their AI if they have one.
@@ -68,6 +86,7 @@
return
set_stance(STANCE_IDLE)
SSai.processing += src
update_paused_hud()
/datum/ai_holder/proc/should_wake()
if(holder.client && !autopilot)
@@ -80,9 +99,7 @@
/datum/ai_holder/proc/forget_everything()
// Some of these might be redundant, but hopefully this prevents future bugs if that changes.
lose_follow()
lose_target()
lose_target_position()
give_up_movement()
remove_target()
// 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly.
/datum/ai_holder/proc/handle_tactics()
@@ -103,39 +120,13 @@
/datum/ai_holder/proc/handle_special_strategical()
/*
//AI Actions
if(!ai_inactive)
//Stanceyness
handle_stance()
//Movement
if(!stop_automated_movement && wander && !anchored) //Allowed to move?
handle_wander_movement()
//Speaking
if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter?
handle_idle_speaking()
//Resisting out buckles
if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY))
handle_resist()
//Resisting out of closets
if(istype(loc,/obj/structure/closet))
var/obj/structure/closet/C = loc
if(C.welded)
resist()
else
C.open()
*/
// For setting the stance WITHOUT processing it
/datum/ai_holder/proc/set_stance(var/new_stance)
ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO)
stance = new_stance
if(stance_coloring) // For debugging or really weird mobs.
stance_color()
update_stance_hud()
// This is called every half a second.
/datum/ai_holder/proc/handle_stance_tactical()
@@ -207,6 +198,7 @@
if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order.
ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE)
if(!target && !find_target())
walk_to_destination()
if(STANCE_FOLLOW)
@@ -233,12 +225,22 @@
ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE)
ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE)
//We got left around for some reason. Goodbye cruel world.
if(!holder)
qdel(src)
ai_log("handle_stance_strategical() : LTT=[lose_target_time]", AI_LOG_TRACE)
if(lose_target_time && (lose_target_time + lose_target_timeout < world.time)) // We were tracking an enemy but they are gone.
ai_log("handle_stance_strategical() : Giving up a chase.", AI_LOG_DEBUG)
remove_target()
if(stance in STANCES_COMBAT)
request_help() // Call our allies.
switch(stance)
if(STANCE_IDLE)
if(speak_chance) // In the long loop since otherwise it wont shut up.
handle_idle_speaking()
if(hostile)
ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE)
find_target()
@@ -246,6 +248,7 @@
if(target)
ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE)
calculate_path(target)
walk_to_target()
if(STANCE_MOVE)
if(hostile && find_target()) // This will switch its stance.
ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE)
@@ -255,6 +258,7 @@
else if(leader)
ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE)
calculate_path(leader)
walk_to_leader()
ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE)
ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE)
@@ -263,7 +267,7 @@
// Helper proc to turn AI 'busy' mode on or off without having to check if there is an AI, to simplify writing code.
/mob/living/proc/set_AI_busy(value)
if(ai_holder)
ai_holder.busy = value
ai_holder.set_busy(value)
/mob/living/proc/is_AI_busy()
if(!ai_holder)

View File

@@ -10,46 +10,22 @@
var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range.
// This does the actual attacking.
/datum/ai_holder/proc/engage_target()
ai_log("engage_target() : Entering.", AI_LOG_DEBUG)
// Can we still see them?
// if(!target || !can_attack(target) || (!(target in list_targets())) )
if(!target || !can_attack(target))
ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE)
lose_target() // We lost them.
if(!find_target()) // If we can't get a new one, then wait for a bit and then time out.
set_stance(STANCE_IDLE)
lost_target()
ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG)
if(lose_target()) // We lost them (returns TRUE if we found something else to do)
ai_log("engage_target() : Pursuing other options (last seen, or a new target).", AI_LOG_TRACE)
return
// if(lose_target_time + lose_target_timeout < world.time)
// ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE)
// set_stance(STANCE_IDLE) // It must've been the wind.
// lost_target()
// ai_log("engage_target() : Exiting.", AI_LOG_DEBUG)
// return
// // But maybe we do one last ditch effort.
// if(!target_last_seen_turf || intelligence_level < AI_SMART)
// ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE)
// set_stance(STANCE_IDLE)
// else
// ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE)
// engage_unseen_enemy()
else
ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE)
var/distance = get_dist(holder, target)
ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE)
holder.face_atom(target)
last_conflict_time = world.time
request_help() // Call our allies.
// Do a 'special' attack, if one is allowed.
// if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range))
if(holder.ICheckSpecialAttack(target))
@@ -198,12 +174,8 @@
// Make sure we can still chase/attack them.
if(!target || !can_attack(target))
ai_log("walk_to_target() : Lost target.", AI_LOG_INFO)
if(!find_target())
lost_target()
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
lose_target()
return
else
ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO)
// Find out where we're going.
var/get_to = closest_distance(target)
@@ -220,7 +192,6 @@
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
return
// Otherwise keep walking.
if(!stand_ground)
walk_path(target, get_to)

View File

@@ -2,21 +2,21 @@
// Used when a target is out of sight or invisible.
/datum/ai_holder/proc/engage_unseen_enemy()
ai_log("engage_unseen_enemy() : Entering.", AI_LOG_TRACE)
// Lets do some last things before giving up.
if(!ranged)
if(get_dist(holder, target_last_seen_turf > 1)) // We last saw them over there.
if(conserve_ammo || !holder.ICheckRangedAttack(target_last_seen_turf))
if(get_dist(holder, target_last_seen_turf) > 1) // We last saw them over there.
// Go to where you last saw the enemy.
give_destination(target_last_seen_turf, 1, TRUE) // This will set it to STANCE_REPOSITION.
else // We last saw them next to us, so do a blind attack on that tile.
give_destination(target_last_seen_turf, 1, TRUE) // Sets stance as well
else if(lose_target_time == world.time) // We last saw them next to us, so do a blind attack on that tile.
melee_on_tile(target_last_seen_turf)
else if(!conserve_ammo)
else
find_target()
else
shoot_near_turf(target_last_seen_turf)
// This shoots semi-randomly near a specific turf.
/datum/ai_holder/proc/shoot_near_turf(turf/targeted_turf)
if(!ranged)
return // Can't shoot.
if(get_dist(holder, targeted_turf) > max_range(targeted_turf))
return // Too far to shoot.
@@ -32,6 +32,7 @@
// Attempts to attack something on a specific tile.
// TODO: Put on mob/living?
/datum/ai_holder/proc/melee_on_tile(turf/T)
ai_log("melee_on_tile() : Entering.", AI_LOG_TRACE)
var/mob/living/L = locate() in T
if(!L)
T.visible_message("\The [holder] attacks nothing around \the [T].")

View File

@@ -15,7 +15,7 @@
/datum/ai_holder/proc/should_threaten()
if(!threaten)
return FALSE // We don't negotiate.
if(target in attackers)
if(check_attacker(target))
return FALSE // They (or someone like them) attacked us before, escalate immediately.
if(!will_threaten(target))
return FALSE // Pointless to threaten an animal, a mindless drone, or an object.

View File

@@ -74,7 +74,7 @@
ai_log("request_help() : Exiting.", AI_LOG_DEBUG)
// What allies receive when someone else is calling for help.
// What allies receive when someone else is calling for help.1
/datum/ai_holder/proc/help_requested(mob/living/friend)
ai_log("help_requested() : Entering.", AI_LOG_DEBUG)
if(stance == STANCE_SLEEP)
@@ -92,7 +92,9 @@
if(!holder.IIsAlly(friend)) // Extra sanity.
ai_log("help_requested() : Help requested by [friend] but we hate them.", AI_LOG_INFO)
return
if(friend.ai_holder && friend.ai_holder.target && !can_attack(friend.ai_holder.target))
var/their_target = friend?.ai_holder?.target
if(their_target) // They have a target and aren't just shouting for no reason
if(!can_attack(their_target, vision_required = FALSE))
ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO)
return
if(get_dist(holder, friend) <= follow_distance)
@@ -100,16 +102,16 @@
return
if(get_dist(holder, friend) <= vision_range) // Within our sight.
ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO)
if(friend.ai_holder) // AI calling for help.
if(friend.ai_holder.target && can_attack(friend.ai_holder.target)) // Friend wants us to attack their target.
last_conflict_time = world.time // So we attack immediately and not threaten.
give_target(friend.ai_holder.target) // This will set us to the appropiate stance.
give_target(their_target, urgent = TRUE) // This will set us to the appropiate stance.
ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG)
return
// Otherwise they're outside our sight, lack a target, or aren't AI controlled, but within call range.
// So assuming we're AI controlled, we'll go to them and see whats wrong.
ai_log("help_requested() : Help requested by [friend], going to go to friend.", AI_LOG_INFO)
if(their_target)
add_attacker(their_target) // We won't wait and 'warn' them while they're stabbing our ally
set_follow(friend, 10 SECONDS)
ai_log("help_requested() : Exiting.", AI_LOG_DEBUG)

View File

@@ -26,6 +26,7 @@
// Step 1, find out what we can see.
/datum/ai_holder/proc/list_targets()
. = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder.
. -= dview_mob // Not the dview mob either, nerd.
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
@@ -35,6 +36,7 @@
// Step 2, filter down possible targets to things we actually care about.
/datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
ai_log("find_target() : Entered.", AI_LOG_TRACE)
if(!hostile) // So retaliating mobs only attack the thing that hit it.
return null
. = list()
@@ -70,13 +72,17 @@
return chosen_target
// Step 4, give us our selected target.
/datum/ai_holder/proc/give_target(new_target)
/datum/ai_holder/proc/give_target(new_target, urgent = FALSE)
ai_log("give_target() : Given '[new_target]', urgent=[urgent].", AI_LOG_TRACE)
target = new_target
if(target != null)
if(should_threaten())
lose_target_time = 0
track_target_position()
if(should_threaten() && !urgent)
set_stance(STANCE_ALERT)
else
set_stance(STANCE_APPROACH)
set_stance(STANCE_FIGHT)
last_target_time = world.time
return TRUE
@@ -109,8 +115,8 @@
sorted_targets += A
return sorted_targets
/datum/ai_holder/proc/can_attack(atom/movable/the_target)
if(!can_see_target(the_target))
/datum/ai_holder/proc/can_attack(atom/movable/the_target, var/vision_required = TRUE)
if(!can_see_target(the_target) && vision_required)
return FALSE
if(istype(the_target, /mob/zshadow))
@@ -157,20 +163,37 @@
/datum/ai_holder/proc/found(atom/movable/the_target)
return FALSE
//We can't see the target, go look or attack where they were last seen.
// 'Soft' loss of target. They may still exist, we still have some info about them maybe.
/datum/ai_holder/proc/lose_target()
ai_log("lose_target() : Entering.", AI_LOG_TRACE)
if(target)
ai_log("lose_target() : Had a target, setting to null and LTT.", AI_LOG_DEBUG)
target = null
lose_target_time = world.time
give_up_movement()
if(target_last_seen_turf && intelligence_level >= AI_SMART)
ai_log("lose_target() : Going into 'engage unseen enemy' mode.", AI_LOG_INFO)
engage_unseen_enemy()
return TRUE //We're still working on it
else
ai_log("lose_target() : Can't chase target, so giving up.", AI_LOG_INFO)
remove_target()
return find_target() //Returns if we found anything else to do
//Target is no longer valid (?)
/datum/ai_holder/proc/lost_target()
set_stance(STANCE_IDLE)
return FALSE //Nothing new to do
// 'Hard' loss of target. Clean things up and return to idle.
/datum/ai_holder/proc/remove_target()
ai_log("remove_target() : Entering.", AI_LOG_TRACE)
if(target)
target = null
lose_target_time = 0
give_up_movement()
lose_target_position()
lose_target()
set_stance(STANCE_IDLE)
// Check if target is visible to us.
/datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range)
@@ -235,7 +258,7 @@
ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE)
on_attacked(attacker) // So we attack immediately and not threaten.
return FALSE
else if(attacker in attackers && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip
else if(check_attacker(attacker) && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip
ai_log("react_to_attack() : Was attacked by [attacker]. Can retaliate, waited 3 seconds.", AI_LOG_INFO)
on_attacked(attacker) // So we attack immediately and not threaten.
return give_target(attacker) // Also handles setting the appropiate stance.
@@ -246,15 +269,24 @@
ai_log("react_to_attack() : Was attacked by [attacker].", AI_LOG_INFO)
on_attacked(attacker) // So we attack immediately and not threaten.
return give_target(attacker) // Also handles setting the appropiate stance.
return give_target(attacker, urgent = TRUE) // Also handles setting the appropiate stance.
// Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before.
/datum/ai_holder/proc/on_attacked(atom/movable/AM)
if(isliving(AM))
var/mob/living/L = AM
if(!(L.name in attackers))
attackers |= L.name
last_conflict_time = world.time
add_attacker(AM)
// Checks to see if an atom attacked us lately
/datum/ai_holder/proc/check_attacker(var/atom/movable/A)
return (A in attackers)
// We were attacked by this thing recently
/datum/ai_holder/proc/add_attacker(var/atom/movable/A)
attackers |= A.name
// Forgive this attacker
/datum/ai_holder/proc/remove_attacker(var/atom/movable/A)
attackers -= A.name
// Causes targeting to prefer targeting the taunter if possible.
// This generally occurs if more than one option is within striking distance, including the taunter.

View File

@@ -320,6 +320,14 @@
if(inactivity > duration) return inactivity
return 0
//Called when the client performs a drag-and-drop operation.
/client/MouseDrop(start_object,end_object,start_location,end_location,start_control,end_control,params)
if(buildmode && start_control == "mapwindow.map" && start_control == end_control)
build_drag(src,buildmode,start_object,end_object,start_location,end_location,start_control,end_control,params)
else
. = ..()
// Byond seemingly calls stat, each tick.
// Calling things each tick can get expensive real quick.
// So we slow this down a little.

View File

@@ -40,7 +40,7 @@
pref.backbag = 1 //Same as above
character.backbag = pref.backbag
if(pref.pdachoice > 4 || pref.pdachoice < 1)
if(pref.pdachoice > 5 || pref.pdachoice < 1)
pref.pdachoice = 1
character.pdachoice = pref.pdachoice

View File

@@ -128,6 +128,12 @@ var/list/_client_preferences_by_type
enabled_description = "Show"
disabled_description = "Hide"
/datum/client_preference/precision_placement
description ="Precision Placement"
key = "PRECISE_PLACEMENT"
enabled_description = "Active"
disabled_description = "Inactive"
/datum/client_preference/hotkeys_default
description ="Hotkeys Default"
key = "HUD_HOTKEYS"

View File

@@ -257,12 +257,12 @@ var/list/gear_datums = list()
/datum/gear/proc/spawn_item(var/location, var/metadata)
var/datum/gear_data/gd = new(path, location)
if(length(gear_tweaks) && metadata)
for(var/datum/gear_tweak/gt in gear_tweaks)
if(gear_tweaks.len)
gt.tweak_gear_data(metadata["[gt]"], gd)
var/item = new gd.path(gd.location)
if(length(gear_tweaks) && metadata)
for(var/datum/gear_tweak/gt in gear_tweaks)
if(gear_tweaks.len)
gt.tweak_item(item, metadata["[gt]"])
var/mob/M = location
if(istype(M) && exploitable) //Update exploitable info records for the mob without creating a duplicate object at their feet.

View File

@@ -204,7 +204,7 @@
/datum/gear/uniform/dept/undercoat/mining
display_name = "mining undercoat (Teshari)"
path = /obj/item/clothing/accessory/poncho/roles/cloak/mining
path = /obj/item/clothing/under/seromi/undercoat/jobs/mining
allowed_roles = list("Quartermaster","Shaft Miner")
/datum/gear/uniform/dept/undercoat/security

View File

@@ -99,7 +99,7 @@
if((pref.job_civilian_low & ASSISTANT) && job.type != /datum/job/assistant)
. += "<font color=grey>[rank]</font></a></td><td></td></tr>"
continue
if((rank in command_positions) || (rank == "AI"))//Bold head jobs
if((rank in SSjob.get_job_titles_in_department(DEPARTMENT_COMMAND) ) || (rank == "AI"))//Bold head jobs
. += "<b>[rank]</b></a>"
else
. += "[rank]</a>"

View File

@@ -89,6 +89,21 @@
feedback_add_details("admin_verb","TLOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_precision_placement()
set name = "Enable/Disable Precision Placement"
set category = "Preferences"
set desc = "Toggles precise placement of objects on tables."
var/pref_path = /datum/client_preference/precision_placement
toggle_preference(pref_path)
to_chat(src,"You will [ (is_preference_enabled(pref_path)) ? "now" : "no longer"] place items where your cursor is on the table.")
SScharacter_setup.queue_preferences_save(prefs)
feedback_add_details("admin_verb","TPIP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_typing()
set name = "Show/Hide Typing Indicator"
set category = "Preferences"

View File

@@ -214,6 +214,18 @@
var/mob/M = src.loc
M.update_inv_back()
/obj/item/weapon/storage/backpack/chameleon/full
starts_with = list(
/obj/item/clothing/under/chameleon,
/obj/item/clothing/head/chameleon,
/obj/item/clothing/suit/chameleon,
/obj/item/clothing/shoes/chameleon,
/obj/item/clothing/gloves/chameleon,
/obj/item/clothing/mask/chameleon,
/obj/item/clothing/glasses/chameleon,
/obj/item/clothing/accessory/chameleon
)
//********************
//**Chameleon Gloves**
//********************

View File

@@ -35,7 +35,7 @@
/obj/item/clothing/head/helmet/space/verb/toggle_camera()
set name = "Toggle Helmet Camera"
set desc = "Turn your helmet's camera on or off."
set category = "Object"
set category = "Hardsuit"
set src in usr
if(usr.stat || usr.restrained() || usr.incapacitated())
return

View File

@@ -205,15 +205,15 @@
/obj/item/clothing/accessory/armor/armguards/laserproof
name = "ablative arm guards"
desc = "These arm guards will protect your arms from energy weapons."
icon_state = "armguards_laser"
icon_state = "armguards_ablative"
item_state_slots = list(slot_r_hand_str = "swat", slot_l_hand_str = "swat")
siemens_coefficient = 0.4 //This is worse than the other ablative pieces, to avoid this from becoming the poor warden's insulated gloves.
siemens_coefficient = 0.1 //These don't cover the hands, so the siemens doesn't need to be worse than normal ablative.
armor = list(melee = 10, bullet = 10, laser = 80, energy = 50, bomb = 0, bio = 0, rad = 0)
/obj/item/clothing/accessory/armor/armguards/bulletproof
name = "bullet resistant arm guards"
desc = "These arm guards will protect your arms from ballistic weapons."
icon_state = "armguards_bullet"
icon_state = "armguards_ballistic"
item_state_slots = list(slot_r_hand_str = "swat", slot_l_hand_str = "swat")
siemens_coefficient = 0.7
armor = list(melee = 10, bullet = 80, laser = 10, energy = 50, bomb = 0, bio = 0, rad = 0)
@@ -264,7 +264,7 @@
/obj/item/clothing/accessory/armor/legguards/laserproof
name = "ablative leg guards"
desc = "These will protect your legs from energy weapons."
icon_state = "legguards_laser"
icon_state = "legguards_ablative"
item_state_slots = list(slot_r_hand_str = "jackboots", slot_l_hand_str = "jackboots")
siemens_coefficient = 0.1
armor = list(melee = 10, bullet = 10, laser = 80, energy = 50, bomb = 0, bio = 0, rad = 0)
@@ -272,7 +272,7 @@
/obj/item/clothing/accessory/armor/legguards/bulletproof
name = "bullet resistant leg guards"
desc = "These will protect your legs from ballistic weapons."
icon_state = "legguards_bullet"
icon_state = "legguards_ballistic"
item_state_slots = list(slot_r_hand_str = "jackboots", slot_l_hand_str = "jackboots")
siemens_coefficient = 0.7
armor = list(melee = 10, bullet = 80, laser = 10, energy = 10, bomb = 0, bio = 0, rad = 0)

View File

@@ -5,6 +5,8 @@
slot = ACCESSORY_SLOT_TORSO //Legacy/balance purposes
concealed_holster = 1
var/obj/item/holstered = null
var/holster_in = 'sound/items/holsterin.ogg'
var/holster_out = 'sound/items/holsterout.ogg'
/obj/item/clothing/accessory/holster/proc/holster(var/obj/item/I, var/mob/living/user)
if(holstered && istype(user))
@@ -15,6 +17,9 @@
to_chat(user, "<span class='warning'>[I] won't fit in [src]!</span>")
return
if(holster_in)
playsound(get_turf(src), holster_in, 50)
if(istype(user))
user.stop_aiming(no_message=1)
holstered = I
@@ -36,7 +41,9 @@
if(istype(user.get_active_hand(),/obj) && istype(user.get_inactive_hand(),/obj))
to_chat(user, "<span class='warning'>You need an empty hand to draw \the [holstered]!</span>")
else
var/sound_vol = 25
if(user.a_intent == I_HURT)
sound_vol = 50
usr.visible_message(
"<span class='danger'>[user] draws \the [holstered], ready to shoot!</span>",
"<span class='warning'>You draw \the [holstered], ready to shoot!</span>"
@@ -46,6 +53,10 @@
"<span class='notice'>[user] draws \the [holstered], pointing it at the ground.</span>",
"<span class='notice'>You draw \the [holstered], pointing it at the ground.</span>"
)
if(holster_out)
playsound(get_turf(src), holster_out, sound_vol)
user.put_in_hands(holstered)
holstered.add_fingerprint(user)
w_class = initial(w_class)

View File

@@ -206,16 +206,16 @@ var/list/event_last_fired = list()
else if(istype(R.module, /obj/item/weapon/robot_module/robot/research))
active_with_role["Scientist"]++
if(M.mind.assigned_role in engineering_positions)
if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING))
active_with_role["Engineer"]++
if(M.mind.assigned_role in medical_positions)
if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_MEDICAL))
active_with_role["Medical"]++
if(M.mind.assigned_role in security_positions)
if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY))
active_with_role["Security"]++
if(M.mind.assigned_role in science_positions)
if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_RESEARCH))
active_with_role["Scientist"]++
if(M.mind.assigned_role == "AI")

View File

@@ -647,6 +647,10 @@ Drinks Data
glass_icon_state = "appleade"
glass_center_of_mass = list("x"=16, "y"=8)
/datum/reagent/drink/mintapplesparkle
glass_icon_state = "mintapplesparkle"
glass_center_of_mass = list("x"=16, "y"=8)
/datum/reagent/drink/soda/pineappleade
glass_icon_state = "pineappleade"
glass_center_of_mass = list("x"=16, "y"=8)

View File

@@ -510,6 +510,25 @@
src.name = "Frosted Jelly Donut"
reagents.add_reagent("sprinkles", 2)
/obj/item/weapon/reagent_containers/food/snacks/donut/poisonberry
name = "Jelly Donut"
desc = "You jelly?"
icon_state = "jdonut1"
filling_color = "#ED1169"
center_of_mass = list("x"=16, "y"=11)
nutriment_amt = 3
/obj/item/weapon/reagent_containers/food/snacks/donut/poisonberry/Initialize()
. = ..()
reagents.add_reagent("sprinkles", 1)
reagents.add_reagent("poisonberryjuice", 5)
bitesize = 5
if(prob(30))
src.icon_state = "jdonut2"
src.overlay_state = "box-donut2"
src.name = "Frosted Jelly Donut"
reagents.add_reagent("sprinkles", 2)
/obj/item/weapon/reagent_containers/food/snacks/donut/slimejelly
name = "Jelly Donut"
desc = "You jelly?"
@@ -1105,9 +1124,16 @@
/obj/item/weapon/reagent_containers/food/snacks/berryclafoutis/Initialize()
. = ..()
reagents.add_reagent("berryjuice", 5)
bitesize = 3
/obj/item/weapon/reagent_containers/food/snacks/berryclafoutis/berry/Initialize()
. = ..()
reagents.add_reagent("berryjuice", 5)
/obj/item/weapon/reagent_containers/food/snacks/berryclafoutis/poison/Initialize()
. = ..()
reagents.add_reagent("poisonberryjuice", 5)
/obj/item/weapon/reagent_containers/food/snacks/waffles
name = "waffles"
desc = "Mmm, waffles"
@@ -1629,7 +1655,7 @@
/obj/item/weapon/reagent_containers/food/snacks/slimesoup
name = "slime soup"
desc = "If no water is available, you may substitute tears."
icon_state = "slimesoup" //nonexistant?
icon_state = "rorosoup" //nonexistant? - 3/1/2020 FIXED. roro's live on.
filling_color = "#C4DBA0"
/obj/item/weapon/reagent_containers/food/snacks/slimesoup/Initialize()
@@ -3888,13 +3914,21 @@
filling_color = "#E0CF9B"
center_of_mass = list("x"=17, "y"=4)
nutriment_amt = 6
nutriment_desc = list("sweetness" = 2, "muffin" = 2, "berries" = 2)
nutriment_desc = list("sweetness" = 2, "muffin" = 2)
/obj/item/weapon/reagent_containers/food/snacks/berrymuffin/Initialize()
. = ..()
reagents.add_reagent("nutriment", 6)
bitesize = 2
/obj/item/weapon/reagent_containers/food/snacks/berrymuffin/berry/Initialize()
. = ..()
reagents.add_reagent("berryjuice", 3)
/obj/item/weapon/reagent_containers/food/snacks/berrymuffin/poison/Initialize()
. = ..()
reagents.add_reagent("poisonberryjuice", 3)
/obj/item/weapon/reagent_containers/food/snacks/ghostmuffin
name = "booberry muffin"
desc = "My stomach is a graveyard! No living being can quench my bloodthirst!"
@@ -3909,6 +3943,16 @@
reagents.add_reagent("nutriment", 6)
bitesize = 2
/obj/item/weapon/reagent_containers/food/snacks/ghostmuffin/berry/Initialize()
. = ..()
reagents.add_reagent("berryjuice", 3)
bitesize = 2
/obj/item/weapon/reagent_containers/food/snacks/ghostmuffin/poison/Initialize()
. = ..()
reagents.add_reagent("poisonberryjuice", 3)
bitesize = 2
/obj/item/weapon/reagent_containers/food/snacks/eggroll
name = "egg roll"
desc = "Free with orders over 10 thalers."

View File

@@ -274,6 +274,6 @@
qdel(W)
stage++
desc = desclist2[stage]
icon_state = "chaoscake_stage-[stage]"
icon_state = "chaoscake_unfinished-[stage]"
else
to_chat(user, "Hmm, doesnt seem like this layer is supposed to be added there?")

View File

@@ -55,6 +55,13 @@ I said no!
)
result = /obj/item/weapon/reagent_containers/food/snacks/donut/jelly
/datum/recipe/microwave/jellydonut/poisonberry
reagents = list("poisonberryjuice" = 5, "sugar" = 5)
items = list(
/obj/item/weapon/reagent_containers/food/snacks/dough
)
result = /obj/item/weapon/reagent_containers/food/snacks/donut/poisonberry
/datum/recipe/microwave/jellydonut/slime
reagents = list("slimejelly" = 5, "sugar" = 5)
items = list(
@@ -308,7 +315,14 @@ I said no!
items = list(
/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough,
)
result = /obj/item/weapon/reagent_containers/food/snacks/berryclafoutis
result = /obj/item/weapon/reagent_containers/food/snacks/berryclafoutis/berry
/datum/recipe/microwave/poisonberryclafoutis
fruit = list("poisonberries" = 1)
items = list(
/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough,
)
result = /obj/item/weapon/reagent_containers/food/snacks/berryclafoutis/poison
/datum/recipe/microwave/wingfangchu
reagents = list("soysauce" = 5)
@@ -639,7 +653,7 @@ I said no!
result = /obj/item/weapon/reagent_containers/food/snacks/toastedsandwich
/datum/recipe/microwave/peanutbutterjellysandwich
reagents = list("berryjuice" = 5, "peanutbutter" = 5)
reagents = list("cherryjelly" = 5, "peanutbutter" = 5)
items = list(
/obj/item/weapon/reagent_containers/food/snacks/slice/bread,
/obj/item/weapon/reagent_containers/food/snacks/slice/bread
@@ -1106,7 +1120,15 @@ I said no!
/obj/item/weapon/reagent_containers/food/snacks/dough
)
fruit = list("berries" = 1)
result = /obj/item/weapon/reagent_containers/food/snacks/berrymuffin
result = /obj/item/weapon/reagent_containers/food/snacks/berrymuffin/berry
/datum/recipe/microwave/poisonberrymuffin
reagents = list("milk" = 5, "sugar" = 5)
items = list(
/obj/item/weapon/reagent_containers/food/snacks/dough
)
fruit = list("poisonberries" = 1)
result = /obj/item/weapon/reagent_containers/food/snacks/berrymuffin/poison
/datum/recipe/microwave/ghostmuffin
reagents = list("milk" = 5, "sugar" = 5)
@@ -1115,7 +1137,16 @@ I said no!
/obj/item/weapon/ectoplasm
)
fruit = list("berries" = 1)
result = /obj/item/weapon/reagent_containers/food/snacks/ghostmuffin
result = /obj/item/weapon/reagent_containers/food/snacks/ghostmuffin/berry
/datum/recipe/microwave/poisonghostmuffin
reagents = list("milk" = 5, "sugar" = 5)
items = list(
/obj/item/weapon/reagent_containers/food/snacks/dough,
/obj/item/weapon/ectoplasm
)
fruit = list("poisonberries" = 1)
result = /obj/item/weapon/reagent_containers/food/snacks/ghostmuffin/poison
/datum/recipe/microwave/eggroll
reagents = list("soysauce" = 10)
@@ -1201,7 +1232,7 @@ I said no!
/datum/recipe/microwave/piginblanket
items = list(
/obj/item/weapon/reagent_containers/food/snacks/sliceable/flatdough,
/obj/item/weapon/reagent_containers/food/snacks/doughslice,
/obj/item/weapon/reagent_containers/food/snacks/sausage
)
result = /obj/item/weapon/reagent_containers/food/snacks/piginblanket

View File

@@ -1,6 +1,6 @@
/datum/gm_action/atmos_leak
name = "atmospherics leak"
departments = list(ROLE_ENGINEERING, ROLE_SYNTHETIC)
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SYNTHETIC)
var/area/target_area // Chosen target area
var/area/target_turf // Chosen target turf in target_area
var/gas_type // Chosen gas to release
@@ -74,4 +74,4 @@
playsound(target_turf, 'sound/effects/smoke.ogg', 50, 1)
/datum/gm_action/atmos_leak/get_weight()
return 15 + (metric.count_people_in_department(ROLE_ENGINEERING) * 10 + metric.count_people_in_department(ROLE_SYNTHETIC) * 30) // Synthetics are counted in higher value because they can wirelessly connect to alarms.
return 15 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10 + metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 30) // Synthetics are counted in higher value because they can wirelessly connect to alarms.

Some files were not shown because too many files have changed in this diff Show More