mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2025-12-17 13:42:44 +00:00
Merge remote-tracking branch 'Meghan-Rossi/master' into job_description_alt
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
11
code/__defines/_protect.dm
Normal file
11
code/__defines/_protect.dm
Normal 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;\
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
7
code/__defines/sqlite_defines.dm
Normal file
7
code/__defines/sqlite_defines.dm
Normal 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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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>"
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
108
code/controllers/subsystems/job.dm
Normal file
108
code/controllers/subsystems/job.dm
Normal 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]")
|
||||
@@ -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)
|
||||
|
||||
187
code/controllers/subsystems/sqlite.dm
Normal file
187
code/controllers/subsystems/sqlite.dm
Normal 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"
|
||||
@@ -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))
|
||||
|
||||
52
code/datums/managed_browsers/_managed_browser.dm
Normal file
52
code/datums/managed_browsers/_managed_browser.dm
Normal 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()
|
||||
147
code/datums/managed_browsers/feedback_form.dm
Normal file
147
code/datums/managed_browsers/feedback_form.dm
Normal 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)
|
||||
162
code/datums/managed_browsers/feedback_viewer.dm
Normal file
162
code/datums/managed_browsers/feedback_viewer.dm
Normal 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()
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
79
code/game/jobs/job/department.dm
Normal file
79
code/game/jobs/job/department.dm
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
code/game/mecha/mecha_helpers.dm
Normal file
11
code/game/mecha/mecha_helpers.dm
Normal 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
|
||||
@@ -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(\
|
||||
|
||||
@@ -207,3 +207,6 @@
|
||||
qdel(src)
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/item/weapon/pen/crayon/attack_self(var/mob/user)
|
||||
return
|
||||
@@ -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.")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
7
code/game/objects/items/tailoring.dm
Normal file
7
code/game/objects/items/tailoring.dm
Normal 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."
|
||||
@@ -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
|
||||
|
||||
72
code/game/objects/items/trash_material.dm
Normal file
72
code/game/objects/items/trash_material.dm
Normal 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)]"
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
366
code/game/objects/structures/salvageable.dm
Normal file
366
code/game/objects/structures/salvageable.dm
Normal 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
|
||||
)
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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].")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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**
|
||||
//********************
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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?")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user