Uploading all files.

This commit is contained in:
Quotefox
2019-09-23 18:26:34 +02:00
commit 7091cdbe8c
7779 changed files with 2601004 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
/datum/netdata //this requires some thought later on but for now it's fine.
var/network_id
var/autopasskey = TRUE
var/list/recipient_ids = list()
var/sender_id
var/broadcast = FALSE //Whether this is a broadcast packet.
var/list/data = list()
var/list/passkey
// Process data before sending it
/datum/netdata/proc/pre_send(datum/component/ntnet_interface/interface)
// Decrypt the passkey.
if(autopasskey)
if(data["encrypted_passkey"] && !passkey)
var/result = XorEncrypt(hextostr(data["encrypted_passkey"], TRUE), SScircuit.cipherkey)
if(length(result) > 1)
passkey = json_decode(XorEncrypt(hextostr(data["encrypted_passkey"], TRUE), SScircuit.cipherkey))
// Encrypt the passkey.
if(!data["encrypted_passkey"] && passkey)
data["encrypted_passkey"] = strtohex(XorEncrypt(json_encode(passkey), SScircuit.cipherkey))
// If there is no sender ID, set the default one.
if(!sender_id && interface)
sender_id = interface.hardware_id
/datum/netdata/proc/standard_format_data(primary, secondary, passkey)
data["data"] = primary
data["data_secondary"] = secondary
data["encrypted_passkey"] = passkey
/datum/netdata/proc/json_to_data(json)
data = json_decode(json)
/datum/netdata/proc/json_append_to_data(json)
data |= json_decode(json)
/datum/netdata/proc/data_to_json()
return json_encode(data)
/datum/netdata/proc/json_list_generation_admin() //for admin logs and such.
. = list()
. |= json_list_generation()
/datum/netdata/proc/json_list_generation()
. = list()
. |= json_list_generation_netlog()
.["network_id"] = network_id
/datum/netdata/proc/json_list_generation_netlog()
. = list()
.["recipient_ids"] = recipient_ids
.["sender_id"] = sender_id
.["data_list"] = data
/datum/netdata/proc/generate_netlog()
return "[json_encode(json_list_generation_netlog())]"
+251
View File
@@ -0,0 +1,251 @@
/datum/ntnet
var/network_id = "Network"
var/list/connected_interfaces_by_id = list() //id = datum/component/ntnet_interface
var/list/services_by_path = list() //type = datum/ntnet_service
var/list/services_by_id = list() //id = datum/ntnet_service
var/list/autoinit_service_paths = list() //typepaths
var/list/relays = list()
var/list/logs = list()
var/list/available_station_software = list()
var/list/available_antag_software = list()
var/list/chat_channels = list()
var/list/fileservers = list()
// Amount of logs the system tries to keep in memory. Keep below 999 to prevent byond from acting weirdly.
// High values make displaying logs much laggier.
var/setting_maxlogcount = 100
// These only affect wireless. LAN (consoles) are unaffected since it would be possible to create scenario where someone turns off NTNet, and is unable to turn it back on since it refuses connections
var/setting_softwaredownload = TRUE
var/setting_peertopeer = TRUE
var/setting_communication = TRUE
var/setting_systemcontrol = TRUE
var/setting_disabled = FALSE // Setting to 1 will disable all wireless, independently on relays status.
var/intrusion_detection_enabled = TRUE // Whether the IDS warning system is enabled
var/intrusion_detection_alarm = FALSE // Set when there is an IDS warning due to malicious (antag) software.
// If new NTNet datum is spawned, it replaces the old one.
/datum/ntnet/New(_netid)
build_software_lists()
add_log("NTNet logging system activated.")
if(_netid)
network_id = _netid
if(!SSnetworks.register_network(src))
stack_trace("Network [type] with ID [network_id] failed to register and has been deleted.")
qdel(src)
/datum/ntnet/Destroy()
for(var/i in connected_interfaces_by_id)
var/datum/component/ntnet_interface/I = i
I.unregister_connection(src)
for(var/i in services_by_id)
var/datum/ntnet_service/S = i
S.disconnect(src, TRUE)
return ..()
/datum/ntnet/proc/interface_connect(datum/component/ntnet_interface/I)
if(connected_interfaces_by_id[I.hardware_id])
return FALSE
connected_interfaces_by_id[I.hardware_id] = I
return TRUE
/datum/ntnet/proc/interface_disconnect(datum/component/ntnet_interface/I)
connected_interfaces_by_id -= I.hardware_id
return TRUE
/datum/ntnet/proc/find_interface_id(id)
return connected_interfaces_by_id[id]
/datum/ntnet/proc/find_service_id(id)
return services_by_id[id]
/datum/ntnet/proc/find_service_path(path)
return services_by_path[path]
/datum/ntnet/proc/register_service(datum/ntnet_service/S)
if(!istype(S))
return FALSE
if(services_by_path[S.type] || services_by_id[S.id])
return FALSE
services_by_path[S.type] = S
services_by_id[S.id] = S
return TRUE
/datum/ntnet/proc/unregister_service(datum/ntnet_service/S)
if(!istype(S))
return FALSE
services_by_path -= S.type
services_by_id -= S.id
return TRUE
/datum/ntnet/proc/create_service(type)
var/datum/ntnet_service/S = new type
if(!istype(S))
return FALSE
. = S.connect(src)
if(!.)
qdel(S)
/datum/ntnet/proc/destroy_service(type)
var/datum/ntnet_service/S = find_service_path(type)
if(!istype(S))
return FALSE
. = S.disconnect(src)
if(.)
qdel(src)
/datum/ntnet/proc/process_data_transmit(datum/component/ntnet_interface/sender, datum/netdata/data)
if(!check_relay_operation())
return FALSE
data.network_id = src
log_data_transfer(data)
var/list/datum/component/ntnet_interface/receiving = list()
if((length(data.recipient_ids == 1) && data.recipient_ids[1] == NETWORK_BROADCAST_ID) || data.recipient_ids == NETWORK_BROADCAST_ID)
data.broadcast = TRUE
for(var/i in connected_interfaces_by_id)
receiving |= connected_interfaces_by_id[i]
else
for(var/i in data.recipient_ids)
var/datum/component/ntnet_interface/receiver = find_interface_id(i)
receiving |= receiver
for(var/i in receiving)
var/datum/component/ntnet_interface/receiver = i
if(receiver)
receiver.__network_receive(data)
for(var/i in services_by_id)
var/datum/ntnet_service/serv = services_by_id[i]
serv.ntnet_intercept(data, src, sender)
return TRUE
/datum/ntnet/proc/check_relay_operation(zlevel) //can be expanded later but right now it's true/false.
for(var/i in relays)
var/obj/machinery/ntnet_relay/n = i
if(zlevel && n.z != zlevel)
continue
if(n.is_operational())
return TRUE
return FALSE
/datum/ntnet/proc/log_data_transfer(datum/netdata/data)
logs += "[STATION_TIME_TIMESTAMP("hh:mm:ss")] - [data.generate_netlog()]"
if(logs.len > setting_maxlogcount)
logs = logs.Copy(logs.len - setting_maxlogcount, 0)
return
// Simplified logging: Adds a log. log_string is mandatory parameter, source is optional.
/datum/ntnet/proc/add_log(log_string, obj/item/computer_hardware/network_card/source = null)
var/log_text = "[STATION_TIME_TIMESTAMP("hh:mm:ss")] - "
if(source)
log_text += "[source.get_network_tag()] - "
else
log_text += "*SYSTEM* - "
log_text += log_string
logs.Add(log_text)
// We have too many logs, remove the oldest entries until we get into the limit
if(logs.len > setting_maxlogcount)
logs = logs.Copy(logs.len-setting_maxlogcount,0)
// Checks whether NTNet operates. If parameter is passed checks whether specific function is enabled.
/datum/ntnet/proc/check_function(specific_action = 0)
if(!relays || !relays.len) // No relays found. NTNet is down
return FALSE
// Check all relays. If we have at least one working relay, network is up.
if(!check_relay_operation())
return FALSE
if(setting_disabled)
return FALSE
switch(specific_action)
if(NTNET_SOFTWAREDOWNLOAD)
return setting_softwaredownload
if(NTNET_PEERTOPEER)
return setting_peertopeer
if(NTNET_COMMUNICATION)
return setting_communication
if(NTNET_SYSTEMCONTROL)
return setting_systemcontrol
return TRUE
// Builds lists that contain downloadable software.
/datum/ntnet/proc/build_software_lists()
available_station_software = list()
available_antag_software = list()
for(var/F in typesof(/datum/computer_file/program))
var/datum/computer_file/program/prog = new F
// Invalid type (shouldn't be possible but just in case), invalid filetype (not executable program) or invalid filename (unset program)
if(!prog || prog.filename == "UnknownProgram" || prog.filetype != "PRG")
continue
// Check whether the program should be available for station/antag download, if yes, add it to lists.
if(prog.available_on_ntnet)
available_station_software.Add(prog)
if(prog.available_on_syndinet)
available_antag_software.Add(prog)
// Attempts to find a downloadable file according to filename var
/datum/ntnet/proc/find_ntnet_file_by_name(filename)
for(var/N in available_station_software)
var/datum/computer_file/program/P = N
if(filename == P.filename)
return P
for(var/N in available_antag_software)
var/datum/computer_file/program/P = N
if(filename == P.filename)
return P
// Resets the IDS alarm
/datum/ntnet/proc/resetIDS()
intrusion_detection_alarm = FALSE
/datum/ntnet/proc/toggleIDS()
resetIDS()
intrusion_detection_enabled = !intrusion_detection_enabled
// Removes all logs
/datum/ntnet/proc/purge_logs()
logs = list()
add_log("-!- LOGS DELETED BY SYSTEM OPERATOR -!-")
// Updates maximal amount of stored logs. Use this instead of setting the number, it performs required checks.
/datum/ntnet/proc/update_max_log_count(lognumber)
if(!lognumber)
return FALSE
// Trim the value if necessary
lognumber = max(MIN_NTNET_LOGS, min(lognumber, MAX_NTNET_LOGS))
setting_maxlogcount = lognumber
add_log("Configuration Updated. Now keeping [setting_maxlogcount] logs in system memory.")
/datum/ntnet/proc/toggle_function(function)
if(!function)
return
function = text2num(function)
switch(function)
if(NTNET_SOFTWAREDOWNLOAD)
setting_softwaredownload = !setting_softwaredownload
add_log("Configuration Updated. Wireless network firewall now [setting_softwaredownload ? "allows" : "disallows"] connection to software repositories.")
if(NTNET_PEERTOPEER)
setting_peertopeer = !setting_peertopeer
add_log("Configuration Updated. Wireless network firewall now [setting_peertopeer ? "allows" : "disallows"] peer to peer network traffic.")
if(NTNET_COMMUNICATION)
setting_communication = !setting_communication
add_log("Configuration Updated. Wireless network firewall now [setting_communication ? "allows" : "disallows"] instant messaging and similar communication services.")
if(NTNET_SYSTEMCONTROL)
setting_systemcontrol = !setting_systemcontrol
add_log("Configuration Updated. Wireless network firewall now [setting_systemcontrol ? "allows" : "disallows"] remote control of station's systems.")
/datum/ntnet/station
network_id = "SS13-NTNET"
/datum/ntnet/station/proc/register_map_supremecy() //called at map init to make this what station networks use.
for(var/obj/machinery/ntnet_relay/R in GLOB.machines)
relays.Add(R)
R.NTNet = src
+116
View File
@@ -0,0 +1,116 @@
// Relays don't handle any actual communication. Global NTNet datum does that, relays only tell the datum if it should or shouldn't work.
/obj/machinery/ntnet_relay
name = "NTNet Quantum Relay"
desc = "A very complex router and transmitter capable of connecting electronic devices together. Looks fragile."
use_power = ACTIVE_POWER_USE
active_power_usage = 10000 //10kW, apropriate for machine that keeps massive cross-Zlevel wireless network operational. Used to be 20 but that actually drained the smes one round
idle_power_usage = 100
icon = 'icons/obj/machines/telecomms.dmi'
icon_state = "bus"
density = TRUE
circuit = /obj/item/circuitboard/machine/ntnet_relay
var/datum/ntnet/NTNet = null // This is mostly for backwards reference and to allow varedit modifications from ingame.
var/enabled = 1 // Set to 0 if the relay was turned off
var/dos_failure = 0 // Set to 1 if the relay failed due to (D)DoS attack
var/list/dos_sources = list() // Backwards reference for qdel() stuff
var/uid
var/static/gl_uid = 1
// Denial of Service attack variables
var/dos_overload = 0 // Amount of DoS "packets" in this relay's buffer
var/dos_capacity = 500 // Amount of DoS "packets" in buffer required to crash the relay
var/dos_dissipate = 1 // Amount of DoS "packets" dissipated over time.
// TODO: Implement more logic here. For now it's only a placeholder.
/obj/machinery/ntnet_relay/is_operational()
if(stat & (BROKEN | NOPOWER | EMPED))
return FALSE
if(dos_failure)
return FALSE
if(!enabled)
return FALSE
return TRUE
/obj/machinery/ntnet_relay/update_icon()
if(is_operational())
icon_state = "bus"
else
icon_state = "bus_off"
/obj/machinery/ntnet_relay/process()
if(is_operational())
use_power = ACTIVE_POWER_USE
else
use_power = IDLE_POWER_USE
update_icon()
if(dos_overload)
dos_overload = max(0, dos_overload - dos_dissipate)
// If DoS traffic exceeded capacity, crash.
if((dos_overload > dos_capacity) && !dos_failure)
dos_failure = 1
update_icon()
SSnetworks.station_network.add_log("Quantum relay switched from normal operation mode to overload recovery mode.")
// If the DoS buffer reaches 0 again, restart.
if((dos_overload == 0) && dos_failure)
dos_failure = 0
update_icon()
SSnetworks.station_network.add_log("Quantum relay switched from overload recovery mode to normal operation mode.")
..()
/obj/machinery/ntnet_relay/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "ntnet_relay", "NTNet Quantum Relay", 500, 300, master_ui, state)
ui.open()
/obj/machinery/ntnet_relay/ui_data(mob/user)
var/list/data = list()
data["enabled"] = enabled
data["dos_capacity"] = dos_capacity
data["dos_overload"] = dos_overload
data["dos_crashed"] = dos_failure
return data
/obj/machinery/ntnet_relay/ui_act(action, params)
if(..())
return
switch(action)
if("restart")
dos_overload = 0
dos_failure = 0
update_icon()
SSnetworks.station_network.add_log("Quantum relay manually restarted from overload recovery mode to normal operation mode.")
if("toggle")
enabled = !enabled
SSnetworks.station_network.add_log("Quantum relay manually [enabled ? "enabled" : "disabled"].")
update_icon()
/obj/machinery/ntnet_relay/Initialize()
uid = gl_uid++
component_parts = list()
if(SSnetworks.station_network)
SSnetworks.station_network.relays.Add(src)
NTNet = SSnetworks.station_network
SSnetworks.station_network.add_log("New quantum relay activated. Current amount of linked relays: [NTNet.relays.len]")
. = ..()
/obj/machinery/ntnet_relay/Destroy()
if(SSnetworks.station_network)
SSnetworks.station_network.relays.Remove(src)
SSnetworks.station_network.add_log("Quantum relay connection severed. Current amount of linked relays: [NTNet.relays.len]")
NTNet = null
for(var/datum/computer_file/program/ntnet_dos/D in dos_sources)
D.target = null
D.error = "Connection to quantum relay severed"
return ..()
+38
View File
@@ -0,0 +1,38 @@
/datum/ntnet_service
var/name = "Unidentified Network Service"
var/id
var/list/networks_by_id = list() //Yes we support multinetwork services!
/datum/ntnet_service/New()
var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE)
id = N.hardware_id
/datum/ntnet_service/Destroy()
for(var/i in networks_by_id)
var/datum/ntnet/N = i
disconnect(N, TRUE)
networks_by_id = null
return ..()
/datum/ntnet_service/proc/connect(datum/ntnet/net)
if(!istype(net))
return FALSE
GET_COMPONENT(interface, /datum/component/ntnet_interface)
if(!interface.register_connection(net))
return FALSE
if(!net.register_service(src))
interface.unregister_connection(net)
return FALSE
networks_by_id[net.network_id] = net
return TRUE
/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE)
if(!istype(net) || (!net.unregister_service(src) && !force))
return FALSE
GET_COMPONENT(interface, /datum/component/ntnet_interface)
interface.unregister_connection(net)
networks_by_id -= net.network_id
return TRUE
/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender)
return
+61
View File
@@ -0,0 +1,61 @@
/mob/living/carbon/human/virtual_reality
var/datum/mind/real_mind // where is my mind t. pixies
var/obj/machinery/vr_sleeper/vr_sleeper
var/datum/action/quit_vr/quit_action
/mob/living/carbon/human/virtual_reality/Initialize()
. = ..()
quit_action = new()
quit_action.Grant(src)
/mob/living/carbon/human/virtual_reality/death()
revert_to_reality()
..()
/mob/living/carbon/human/virtual_reality/Destroy()
revert_to_reality()
return ..()
/mob/living/carbon/human/virtual_reality/Life()
. = ..()
if(real_mind)
var/mob/living/real_me = real_mind.current
if (real_me && real_me.stat == CONSCIOUS)
return
revert_to_reality(FALSE)
/mob/living/carbon/human/virtual_reality/ghostize()
stack_trace("Ghostize was called on a virtual reality mob")
/mob/living/carbon/human/virtual_reality/ghost()
set category = "OOC"
set name = "Ghost"
set desc = "Relinquish your life and enter the land of the dead."
revert_to_reality(FALSE)
/mob/living/carbon/human/virtual_reality/proc/revert_to_reality(deathchecks = TRUE)
if(real_mind && mind)
real_mind.current.audiovisual_redirect = null
real_mind.current.ckey = ckey
real_mind.current.stop_sound_channel(CHANNEL_HEARTBEAT)
if(deathchecks && vr_sleeper)
if(vr_sleeper.you_die_in_the_game_you_die_for_real)
to_chat(real_mind, "<span class='warning'>You feel everything fading away...</span>")
real_mind.current.death(0)
if(deathchecks && vr_sleeper)
vr_sleeper.vr_human = null
vr_sleeper = null
real_mind = null
/datum/action/quit_vr
name = "Quit Virtual Reality"
icon_icon = 'icons/mob/actions/actions_vr.dmi'
button_icon_state = "logout"
/datum/action/quit_vr/Trigger()
if(..())
if(istype(owner, /mob/living/carbon/human/virtual_reality))
var/mob/living/carbon/human/virtual_reality/VR = owner
VR.revert_to_reality(FALSE)
else
Remove(owner)
+236
View File
@@ -0,0 +1,236 @@
//Glorified teleporter that puts you in a new human body.
// it's """VR"""
/obj/machinery/vr_sleeper
name = "virtual reality sleeper"
desc = "A sleeper modified to alter the subconscious state of the user, allowing them to visit virtual worlds."
icon = 'icons/obj/machines/sleeper.dmi'
icon_state = "sleeper"
state_open = TRUE
occupant_typecache = list(/mob/living/carbon/human) // turned into typecache in Initialize
circuit = /obj/item/circuitboard/machine/vr_sleeper
var/you_die_in_the_game_you_die_for_real = FALSE
var/datum/effect_system/spark_spread/sparks
var/mob/living/carbon/human/virtual_reality/vr_human
var/vr_category = "default" //Specific category of spawn points to pick from
var/allow_creating_vr_humans = TRUE //So you can have vr_sleepers that always spawn you as a specific person or 1 life/chance vr games
var/only_current_user_can_interact = FALSE
/obj/machinery/vr_sleeper/Initialize()
. = ..()
sparks = new /datum/effect_system/spark_spread()
sparks.set_up(2,0)
sparks.attach(src)
update_icon()
/obj/machinery/vr_sleeper/attackby(obj/item/I, mob/user, params)
if(!state_open && !occupant)
if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I))
return
if(default_change_direction_wrench(user, I))
return
if(default_pry_open(I))
return
if(default_deconstruction_crowbar(I))
return
return ..()
/obj/machinery/vr_sleeper/relaymove(mob/user)
open_machine()
/obj/machinery/vr_sleeper/container_resist(mob/living/user)
open_machine()
/obj/machinery/vr_sleeper/Destroy()
open_machine()
cleanup_vr_human()
QDEL_NULL(sparks)
return ..()
/obj/machinery/vr_sleeper/hugbox
desc = "A sleeper modified to alter the subconscious state of the user, allowing them to visit virtual worlds. Seems slightly more secure."
flags_1 = NODECONSTRUCT_1
only_current_user_can_interact = TRUE
/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user)
return
/obj/machinery/vr_sleeper/emag_act(mob/user)
you_die_in_the_game_you_die_for_real = TRUE
sparks.start()
addtimer(CALLBACK(src, .proc/emagNotify), 150)
/obj/machinery/vr_sleeper/update_icon()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
/obj/machinery/vr_sleeper/open_machine()
if(!state_open)
if(vr_human)
vr_human.revert_to_reality(FALSE)
if(occupant)
SStgui.close_user_uis(occupant, src)
..()
/obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
close_machine(target)
/obj/machinery/vr_sleeper/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "vr_sleeper", "VR Sleeper", 475, 340, master_ui, state)
ui.open()
/obj/machinery/vr_sleeper/ui_act(action, params)
if(..())
return
switch(action)
if("vr_connect")
var/mob/living/carbon/human/human_occupant = occupant
if(human_occupant && human_occupant.mind && usr == occupant)
to_chat(occupant, "<span class='warning'>Transferring to virtual reality...</span>")
if(vr_human && vr_human.stat == CONSCIOUS && !vr_human.real_mind)
SStgui.close_user_uis(occupant, src)
human_occupant.audiovisual_redirect = vr_human
vr_human.real_mind = human_occupant.mind
vr_human.ckey = human_occupant.ckey
to_chat(vr_human, "<span class='notice'>Transfer successful! You are now playing as [vr_human] in VR!</span>")
else
if(allow_creating_vr_humans)
to_chat(occupant, "<span class='warning'>Virtual avatar not found, attempting to create one...</span>")
var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint()
var/turf/T = get_turf(V)
if(T)
SStgui.close_user_uis(occupant, src)
build_virtual_human(occupant, T, V.vr_outfit)
to_chat(vr_human, "<span class='notice'>Transfer successful! You are now playing as [vr_human] in VR!</span>")
else
to_chat(occupant, "<span class='warning'>Virtual world misconfigured, aborting transfer</span>")
else
to_chat(occupant, "<span class='warning'>The virtual world does not support the creation of new virtual avatars, aborting transfer</span>")
. = TRUE
if("delete_avatar")
if(!occupant || usr == occupant)
if(vr_human)
cleanup_vr_human()
else
to_chat(usr, "<span class='warning'>The VR Sleeper's safeties prevent you from doing that.</span>")
. = TRUE
if("toggle_open")
if(state_open)
close_machine()
else if ((!occupant || usr == occupant) || !only_current_user_can_interact)
open_machine()
. = TRUE
/obj/machinery/vr_sleeper/ui_data(mob/user)
var/list/data = list()
if(vr_human && !QDELETED(vr_human))
data["can_delete_avatar"] = TRUE
var/status
switch(vr_human.stat)
if(CONSCIOUS)
status = "Conscious"
if(DEAD)
status = "Dead"
if(UNCONSCIOUS)
status = "Unconscious"
if(SOFT_CRIT)
status = "Barely Conscious"
data["vr_avatar"] = list("name" = vr_human.name, "status" = status, "health" = vr_human.health, "maxhealth" = vr_human.maxHealth)
data["toggle_open"] = state_open
data["emagged"] = you_die_in_the_game_you_die_for_real
data["isoccupant"] = (user == occupant)
return data
/obj/machinery/vr_sleeper/proc/get_vr_spawnpoint() //proc so it can be overridden for team games or something
return safepick(GLOB.vr_spawnpoints[vr_category])
/obj/machinery/vr_sleeper/proc/build_spawnpoints() // used to rebuild the list for admins if need be
GLOB.vr_spawnpoints = list()
for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list)
GLOB.vr_spawnpoints[V.vr_category] = V
/obj/machinery/vr_sleeper/proc/build_virtual_human(mob/living/carbon/human/H, location, var/datum/outfit/outfit, transfer = TRUE)
if(H)
cleanup_vr_human()
vr_human = new /mob/living/carbon/human/virtual_reality(location)
vr_human.mind_initialize()
vr_human.vr_sleeper = src
vr_human.real_mind = H.mind
H.dna.transfer_identity(vr_human)
vr_human.name = H.name
vr_human.real_name = H.real_name
vr_human.socks = H.socks
vr_human.socks_color = H.socks_color
vr_human.undershirt = H.undershirt
vr_human.shirt_color = H.shirt_color
vr_human.underwear = H.underwear
vr_human.undie_color = H.undie_color
vr_human.updateappearance(TRUE, TRUE, TRUE)
vr_human.give_genitals(TRUE) //CITADEL ADD
if(outfit)
var/datum/outfit/O = new outfit()
O.equip(vr_human)
if(transfer && H.mind)
SStgui.close_user_uis(H, src)
H.audiovisual_redirect = vr_human
vr_human.ckey = H.ckey
/obj/machinery/vr_sleeper/proc/cleanup_vr_human()
if(vr_human)
vr_human.vr_sleeper = null // Prevents race condition where a new human could get created out of order and set to null.
QDEL_NULL(vr_human)
/obj/machinery/vr_sleeper/proc/emagNotify()
if(vr_human)
vr_human.Dizzy(10)
/obj/effect/landmark/vr_spawn //places you can spawn in VR, auto selected by the vr_sleeper during get_vr_spawnpoint()
var/vr_category = "default" //So we can have specific sleepers, eg: "Basketball VR Sleeper", etc.
var/vr_outfit = /datum/outfit/vr
/obj/effect/landmark/vr_spawn/Initialize()
. = ..()
LAZYADD(GLOB.vr_spawnpoints[vr_category], src)
/obj/effect/landmark/vr_spawn/Destroy()
LAZYREMOVE(GLOB.vr_spawnpoints[vr_category], src)
return ..()
/obj/effect/landmark/vr_spawn/team_1
vr_category = "team_1"
/obj/effect/landmark/vr_spawn/team_2
vr_category = "team_2"
/obj/effect/landmark/vr_spawn/admin
vr_category = "event"
/obj/effect/landmark/vr_spawn/syndicate // Multiple missions will use syndicate gear
vr_outfit = /datum/outfit/vr/syndicate
/obj/effect/vr_clean_master // Will keep VR areas that have this relatively clean.
icon = 'icons/mob/screen_gen.dmi'
icon_state = "x2"
color = "#00FF00"
invisibility = INVISIBILITY_ABSTRACT
var/area/vr_area
/obj/effect/vr_clean_master/Initialize()
. = ..()
vr_area = get_area(src)
addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)
/obj/effect/vr_clean_master/proc/clean_up()
if (vr_area)
for (var/obj/item/ammo_casing/casing in vr_area)
qdel(casing)
for(var/obj/effect/decal/cleanable/C in vr_area)
qdel(C)
for (var/mob/living/carbon/human/virtual_reality/H in vr_area)
if (H.stat == DEAD && !H.vr_sleeper && !H.real_mind)
qdel(H)
addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)
+561
View File
@@ -0,0 +1,561 @@
#define MAX_ADMIN_BANS_PER_ADMIN 1
#define MAX_ADMIN_BANS_PER_HEADMIN 3
//Either pass the mob you wish to ban in the 'banned_mob' attribute, or the banckey, banip and bancid variables. If both are passed, the mob takes priority! If a mob is not passed, banckey is the minimum that needs to be passed! banip and bancid are optional.
/datum/admins/proc/DB_ban_record(bantype, mob/banned_mob, duration = -1, reason, job = "", bankey = null, banip = null, bancid = null)
if(!check_rights(R_BAN))
return
if(!SSdbcore.Connect())
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
return
var/bantype_pass = 0
var/bantype_str
var/maxadminbancheck //Used to limit the number of active bans of a certein type that each admin can give. Used to protect against abuse or mutiny.
var/announceinirc //When set, it announces the ban in irc. Intended to be a way to raise an alarm, so to speak.
var/blockselfban //Used to prevent the banning of yourself.
var/kickbannedckey //Defines whether this proc should kick the banned person, if they are connected (if banned_mob is defined).
//some ban types kick players after this proc passes (tempban, permaban), but some are specific to db_ban, so
//they should kick within this proc.
switch(bantype)
if(BANTYPE_PERMA)
bantype_str = "PERMABAN"
duration = -1
bantype_pass = 1
blockselfban = 1
if(BANTYPE_TEMP)
bantype_str = "TEMPBAN"
bantype_pass = 1
blockselfban = 1
if(BANTYPE_JOB_PERMA)
bantype_str = "JOB_PERMABAN"
duration = -1
bantype_pass = 1
if(BANTYPE_JOB_TEMP)
bantype_str = "JOB_TEMPBAN"
bantype_pass = 1
if(BANTYPE_ADMIN_PERMA)
bantype_str = "ADMIN_PERMABAN"
duration = -1
bantype_pass = 1
maxadminbancheck = 1
announceinirc = 1
blockselfban = 1
kickbannedckey = 1
if(BANTYPE_ADMIN_TEMP)
bantype_str = "ADMIN_TEMPBAN"
bantype_pass = 1
maxadminbancheck = 1
announceinirc = 1
blockselfban = 1
kickbannedckey = 1
if( !bantype_pass )
return
if( !istext(reason) )
return
if( !isnum(duration) )
return
var/ckey
var/computerid
var/ip
if(ismob(banned_mob))
ckey = banned_mob.ckey
bankey = banned_mob.key
if(banned_mob.client)
computerid = banned_mob.client.computer_id
ip = banned_mob.client.address
else
computerid = banned_mob.computer_id
ip = banned_mob.lastKnownIP
else if(bankey)
ckey = ckey(bankey)
computerid = bancid
ip = banip
var/had_banned_mob = banned_mob != null
var/client/banned_client = banned_mob?.client
var/banned_mob_guest_key = had_banned_mob && IsGuestKey(banned_mob.key)
banned_mob = null
var/sql_ckey = sanitizeSQL(ckey)
var/datum/DBQuery/query_add_ban_get_ckey = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'")
if(!query_add_ban_get_ckey.warn_execute())
qdel(query_add_ban_get_ckey)
return
var/seen_before = query_add_ban_get_ckey.NextRow()
qdel(query_add_ban_get_ckey)
if(!seen_before)
if(!had_banned_mob || (had_banned_mob && !banned_mob_guest_key))
if(alert(usr, "[bankey] has not been seen before, are you sure you want to create a ban for them?", "Unknown ckey", "Yes", "No", "Cancel") != "Yes")
return
var/a_key
var/a_ckey
var/a_computerid
var/a_ip
if(istype(owner))
a_key = owner.key
a_ckey = owner.ckey
a_computerid = owner.computer_id
a_ip = owner.address
if(blockselfban)
if(a_ckey == ckey)
to_chat(usr, "<span class='danger'>You cannot apply this ban type on yourself.</span>")
return
var/who
for(var/client/C in GLOB.clients)
if(!who)
who = "[C]"
else
who += ", [C]"
var/adminwho
for(var/client/C in GLOB.admins)
if(!adminwho)
adminwho = "[C]"
else
adminwho += ", [C]"
reason = sanitizeSQL(reason)
var/sql_a_ckey = sanitizeSQL(a_ckey)
if(maxadminbancheck)
var/datum/DBQuery/query_check_adminban_amt = SSdbcore.NewQuery("SELECT count(id) AS num FROM [format_table_name("ban")] WHERE (a_ckey = '[sql_a_ckey]') AND (bantype = 'ADMIN_PERMABAN' OR (bantype = 'ADMIN_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_check_adminban_amt.warn_execute())
qdel(query_check_adminban_amt)
return
if(query_check_adminban_amt.NextRow())
var/adm_bans = text2num(query_check_adminban_amt.item[1])
var/max_bans = MAX_ADMIN_BANS_PER_ADMIN
if (check_rights(R_PERMISSIONS, FALSE))
max_bans = MAX_ADMIN_BANS_PER_HEADMIN
if(adm_bans >= max_bans)
to_chat(usr, "<span class='danger'>You already logged [max_bans] admin ban(s) or more. Do not abuse this function!</span>")
qdel(query_check_adminban_amt)
return
qdel(query_check_adminban_amt)
if(!computerid)
computerid = "0"
if(!ip)
ip = "0.0.0.0"
var/sql_job = sanitizeSQL(job)
var/sql_computerid = sanitizeSQL(computerid)
var/sql_ip = sanitizeSQL(ip)
var/sql_a_computerid = sanitizeSQL(a_computerid)
var/sql_a_ip = sanitizeSQL(a_ip)
var/sql = "INSERT INTO [format_table_name("ban")] (`bantime`,`server_ip`,`server_port`,`round_id`,`bantype`,`reason`,`job`,`duration`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]', '[bantype_str]', '[reason]', '[sql_job]', [(duration)?"[duration]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, '[sql_ckey]', '[sql_computerid]', INET_ATON('[sql_ip]'), '[sql_a_ckey]', '[sql_a_computerid]', INET_ATON('[sql_a_ip]'), '[who]', '[adminwho]')"
var/datum/DBQuery/query_add_ban = SSdbcore.NewQuery(sql)
if(!query_add_ban.warn_execute())
qdel(query_add_ban)
return
qdel(query_add_ban)
to_chat(usr, "<span class='adminnotice'>Ban saved to database.</span>")
var/msg = "[key_name_admin(usr)] has added a [bantype_str] for [bankey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database."
message_admins(msg,1)
var/datum/admin_help/AH = admin_ticket_log(ckey, msg)
if(announceinirc)
send2irc("BAN ALERT","[a_key] applied a [bantype_str] on [bankey]")
if(kickbannedckey)
if(AH)
AH.Resolve() //with prejudice
if(banned_client && banned_client.ckey == ckey)
qdel(banned_client)
return 1
/datum/admins/proc/DB_ban_unban(ckey, bantype, job = "")
if(!check_rights(R_BAN))
return
var/bantype_str
if(bantype)
var/bantype_pass = 0
switch(bantype)
if(BANTYPE_PERMA)
bantype_str = "PERMABAN"
bantype_pass = 1
if(BANTYPE_TEMP)
bantype_str = "TEMPBAN"
bantype_pass = 1
if(BANTYPE_JOB_PERMA)
bantype_str = "JOB_PERMABAN"
bantype_pass = 1
if(BANTYPE_JOB_TEMP)
bantype_str = "JOB_TEMPBAN"
bantype_pass = 1
if(BANTYPE_ADMIN_PERMA)
bantype_str = "ADMIN_PERMABAN"
bantype_pass = 1
if(BANTYPE_ADMIN_TEMP)
bantype_str = "ADMIN_TEMPBAN"
bantype_pass = 1
if(BANTYPE_ANY_FULLBAN)
bantype_str = "ANY"
bantype_pass = 1
if(BANTYPE_ANY_JOB)
bantype_str = "ANYJOB"
bantype_pass = 1
if( !bantype_pass )
return
var/bantype_sql
if(bantype_str == "ANY")
bantype_sql = "(bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now() ) )"
else if(bantype_str == "ANYJOB")
bantype_sql = "(bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now() ) )"
else
bantype_sql = "bantype = '[bantype_str]'"
var/sql_ckey = sanitizeSQL(ckey)
var/sql = "SELECT id FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND [bantype_sql] AND (unbanned is null OR unbanned = false)"
if(job)
var/sql_job = sanitizeSQL(job)
sql += " AND job = '[sql_job]'"
if(!SSdbcore.Connect())
return
var/ban_id
var/ban_number = 0 //failsafe
var/datum/DBQuery/query_unban_get_id = SSdbcore.NewQuery(sql)
if(!query_unban_get_id.warn_execute())
qdel(query_unban_get_id)
return
while(query_unban_get_id.NextRow())
ban_id = query_unban_get_id.item[1]
ban_number++;
qdel(query_unban_get_id)
if(ban_number == 0)
to_chat(usr, "<span class='danger'>Database update failed due to no bans fitting the search criteria. If this is not a legacy ban you should contact the database admin.</span>")
return
if(ban_number > 1)
to_chat(usr, "<span class='danger'>Database update failed due to multiple bans fitting the search criteria. Note down the ckey, job and current time and contact the database admin.</span>")
return
if(istext(ban_id))
ban_id = text2num(ban_id)
if(!isnum(ban_id))
to_chat(usr, "<span class='danger'>Database update failed due to a ban ID mismatch. Contact the database admin.</span>")
return
DB_ban_unban_by_id(ban_id)
/datum/admins/proc/DB_ban_edit(banid = null, param = null)
if(!check_rights(R_BAN))
return
if(!isnum(banid) || !istext(param))
to_chat(usr, "Cancelled")
return
var/datum/DBQuery/query_edit_ban_get_details = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), duration, reason FROM [format_table_name("ban")] WHERE id = [banid]")
if(!query_edit_ban_get_details.warn_execute())
qdel(query_edit_ban_get_details)
return
var/e_key = usr.key //Editing admin key
var/p_key //(banned) Player key
var/duration //Old duration
var/reason //Old reason
if(query_edit_ban_get_details.NextRow())
p_key = query_edit_ban_get_details.item[1]
duration = query_edit_ban_get_details.item[2]
reason = query_edit_ban_get_details.item[3]
else
to_chat(usr, "Invalid ban id. Contact the database admin")
qdel(query_edit_ban_get_details)
return
qdel(query_edit_ban_get_details)
reason = sanitizeSQL(reason)
var/value
switch(param)
if("reason")
if(!value)
value = input("Insert the new reason for [p_key]'s ban", "New Reason", "[reason]", null) as null|text
value = sanitizeSQL(value)
if(!value)
to_chat(usr, "Cancelled")
return
var/datum/DBQuery/query_edit_ban_reason = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET reason = '[value]', edits = CONCAT(edits,'- [e_key] changed ban reason from <cite><b>\\\"[reason]\\\"</b></cite> to <cite><b>\\\"[value]\\\"</b></cite><BR>') WHERE id = [banid]")
if(!query_edit_ban_reason.warn_execute())
qdel(query_edit_ban_reason)
return
qdel(query_edit_ban_reason)
message_admins("[key_name_admin(usr)] has edited a ban for [p_key]'s reason from [reason] to [value]")
if("duration")
if(!value)
value = input("Insert the new duration (in minutes) for [p_key]'s ban", "New Duration", "[duration]", null) as null|num
if(!isnum(value) || !value)
to_chat(usr, "Cancelled")
return
var/datum/DBQuery/query_edit_ban_duration = SSdbcore.NewQuery("UPDATE [format_table_name("ban")] SET duration = [value], edits = CONCAT(edits,'- [e_key] changed ban duration from [duration] to [value]<br>'), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]")
if(!query_edit_ban_duration.warn_execute())
qdel(query_edit_ban_duration)
return
qdel(query_edit_ban_duration)
message_admins("[key_name_admin(usr)] has edited a ban for [p_key]'s duration from [duration] to [value]")
if("unban")
if(alert("Unban [p_key]?", "Unban?", "Yes", "No") == "Yes")
DB_ban_unban_by_id(banid)
return
else
to_chat(usr, "Cancelled")
return
else
to_chat(usr, "Cancelled")
return
/datum/admins/proc/DB_ban_unban_by_id(id)
if(!check_rights(R_BAN))
return
var/sql = "SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey) FROM [format_table_name("ban")] WHERE id = [id]"
if(!SSdbcore.Connect())
return
var/ban_number = 0 //failsafe
var/p_key
var/datum/DBQuery/query_unban_get_ckey = SSdbcore.NewQuery(sql)
if(!query_unban_get_ckey.warn_execute())
qdel(query_unban_get_ckey)
return
while(query_unban_get_ckey.NextRow())
p_key = query_unban_get_ckey.item[1]
ban_number++;
qdel(query_unban_get_ckey)
if(ban_number == 0)
to_chat(usr, "<span class='danger'>Database update failed due to a ban id not being present in the database.</span>")
return
if(ban_number > 1)
to_chat(usr, "<span class='danger'>Database update failed due to multiple bans having the same ID. Contact the database admin.</span>")
return
if(!istype(owner))
return
var/unban_ckey = owner.ckey
var/unban_computerid = owner.computer_id
var/unban_ip = owner.address
var/sql_update = "UPDATE [format_table_name("ban")] SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = '[unban_ckey]', unbanned_computerid = '[unban_computerid]', unbanned_ip = INET_ATON('[unban_ip]') WHERE id = [id]"
var/datum/DBQuery/query_unban = SSdbcore.NewQuery(sql_update)
if(!query_unban.warn_execute())
qdel(query_unban)
return
qdel(query_unban)
message_admins("[key_name_admin(usr)] has lifted [p_key]'s ban.")
/client/proc/DB_ban_panel()
set category = "Admin"
set name = "Banning Panel"
set desc = "Edit admin permissions"
if(!holder)
return
holder.DB_ban_panel()
/datum/admins/proc/DB_ban_panel(playerckey, adminckey, ip, cid, page = 0)
if(!usr.client)
return
if(!check_rights(R_BAN))
return
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
var/output = "<div align='center'><table width='90%'><tr>"
output += "<td width='35%' align='center'>"
output += "<h1>Banning panel</h1>"
output += "</td>"
output += "<td width='65%' align='center' bgcolor='#f9f9f9'>"
output += "<form method='GET' action='?src=[REF(src)]'><b>Add custom ban:</b> (ONLY use this if you can't ban through any other method)"
output += "<input type='hidden' name='src' value='[REF(src)]'>"
output += HrefTokenFormField()
output += "<table width='100%'><tr>"
output += "<td><b>Ban type:</b><select name='dbbanaddtype'>"
output += "<option value=''>--</option>"
output += "<option value='[BANTYPE_PERMA]'>PERMABAN</option>"
output += "<option value='[BANTYPE_TEMP]'>TEMPBAN</option>"
output += "<option value='[BANTYPE_JOB_PERMA]'>JOB PERMABAN</option>"
output += "<option value='[BANTYPE_JOB_TEMP]'>JOB TEMPBAN</option>"
output += "<option value='[BANTYPE_ADMIN_PERMA]'>ADMIN PERMABAN</option>"
output += "<option value='[BANTYPE_ADMIN_TEMP]'>ADMIN TEMPBAN</option>"
output += "</select></td>"
output += "<td><b>Key:</b> <input type='text' name='dbbanaddkey'></td></tr>"
output += "<tr><td><b>IP:</b> <input type='text' name='dbbanaddip'></td>"
output += "<td><b>Computer id:</b> <input type='text' name='dbbanaddcid'></td></tr>"
output += "<tr><td><b>Duration:</b> <input type='text' name='dbbaddduration'></td>"
output += "<tr><td><b>Severity:</b><select name='dbbanaddseverity'>"
output += "<option value=''>--</option>"
output += "<option value='High'>High Severity</option>"
output += "<option value='Medium'>Medium Severity</option>"
output += "<option value='Minor'>Minor Severity</option>"
output += "<option value='None'>No Severity</option>"
output += "<td><b>Job:</b><select name='dbbanaddjob'>"
output += "<option value=''>--</option>"
for(var/j in get_all_jobs())
output += "<option value='[j]'>[j]</option>"
for(var/j in GLOB.nonhuman_positions)
output += "<option value='[j]'>[j]</option>"
for(var/j in list(ROLE_TRAITOR, ROLE_CHANGELING, ROLE_OPERATIVE, ROLE_REV, ROLE_CULTIST, ROLE_WIZARD))
output += "<option value='[j]'>[j]</option>"
output += "</select></td></tr></table>"
output += "<b>Reason:<br></b><textarea name='dbbanreason' cols='50'></textarea><br>"
output += "<input type='submit' value='Add ban'>"
output += "</form>"
output += "</td>"
output += "</tr>"
output += "</table>"
output += "<form method='GET' action='?src=[REF(src)]'><b>Search:</b> "
output += "<input type='hidden' name='src' value='[REF(src)]'>"
output += HrefTokenFormField()
output += "<b>Ckey:</b> <input type='text' name='dbsearchckey' value='[playerckey]'>"
output += "<b>Admin ckey:</b> <input type='text' name='dbsearchadmin' value='[adminckey]'><br>"
output += "<b>IP:</b> <input type='text' name='dbsearchip' value='[ip]'>"
output += "<b>CID:</b> <input type='text' name='dbsearchcid' value='[cid]'>"
output += "<input type='submit' value='search'>"
output += "</form>"
output += "Please note that all jobban bans or unbans are in-effect the following round."
if(adminckey || playerckey || ip || cid)
var/list/searchlist = list()
if(playerckey)
searchlist += "ckey = '[sanitizeSQL(ckey(playerckey))]'"
if(adminckey)
searchlist += "a_ckey = '[sanitizeSQL(ckey(adminckey))]'"
if(ip)
searchlist += "ip = INET_ATON('[sanitizeSQL(ip)]')"
if(cid)
searchlist += "computerid = '[sanitizeSQL(cid)]'"
var/search = searchlist.Join(" AND ")
var/bancount = 0
var/bansperpage = 15
var/pagecount = 0
page = text2num(page)
var/datum/DBQuery/query_count_bans = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("ban")] WHERE [search]")
if(!query_count_bans.warn_execute())
qdel(query_count_bans)
return
if(query_count_bans.NextRow())
bancount = text2num(query_count_bans.item[1])
qdel(query_count_bans)
if(bancount > bansperpage)
output += "<br><b>Page: </b>"
while(bancount > 0)
output+= "|<a href='?_src_=holder;[HrefToken()];dbsearchckey=[playerckey];dbsearchadmin=[adminckey];dbsearchpage=[pagecount]'>[pagecount == page ? "<b>\[[pagecount]\]</b>" : "\[[pagecount]\]"]</a>"
bancount -= bansperpage
pagecount++
output += "|"
var/blcolor = "#ffeeee" //banned light
var/bdcolor = "#ffdddd" //banned dark
var/ulcolor = "#eeffee" //unbanned light
var/udcolor = "#ddffdd" //unbanned dark
output += "<table width='90%' bgcolor='#e3e3e3' cellpadding='5' cellspacing='0' align='center'>"
output += "<tr>"
output += "<th width='25%'><b>TYPE</b></th>"
output += "<th width='20%'><b>CKEY</b></th>"
output += "<th width='20%'><b>TIME APPLIED</b></th>"
output += "<th width='20%'><b>ADMIN</b></th>"
output += "<th width='15%'><b>OPTIONS</b></th>"
output += "</tr>"
var/limit = " LIMIT [bansperpage * page], [bansperpage]"
var/datum/DBQuery/query_search_bans = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), unbanned, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].unbanned_ckey), unbanned_ckey), unbanned_datetime, edits, round_id FROM [format_table_name("ban")] WHERE [search] ORDER BY bantime DESC[limit]")
if(!query_search_bans.warn_execute())
qdel(query_search_bans)
return
while(query_search_bans.NextRow())
var/banid = query_search_bans.item[1]
var/bantime = query_search_bans.item[2]
var/bantype = query_search_bans.item[3]
var/reason = query_search_bans.item[4]
var/job = query_search_bans.item[5]
var/duration = query_search_bans.item[6]
var/expiration = query_search_bans.item[7]
var/ban_key = query_search_bans.item[8]
var/a_key = query_search_bans.item[9]
var/unbanned = query_search_bans.item[10]
var/unban_key = query_search_bans.item[11]
var/unbantime = query_search_bans.item[12]
var/edits = query_search_bans.item[13]
var/round_id = query_search_bans.item[14]
var/lcolor = blcolor
var/dcolor = bdcolor
if(unbanned)
lcolor = ulcolor
dcolor = udcolor
var/typedesc =""
switch(bantype)
if("PERMABAN")
typedesc = "<font color='red'><b>PERMABAN</b></font>"
if("TEMPBAN")
typedesc = "<b>TEMPBAN</b><br><font size='2'>([duration] minutes [(unbanned) ? "" : "(<a href=\"byond://?src=[REF(src)];[HrefToken()];dbbanedit=duration;dbbanid=[banid]\">Edit</a>))"]<br>Expires [expiration]</font>"
if("JOB_PERMABAN")
typedesc = "<b>JOBBAN</b><br><font size='2'>([job])"
if("JOB_TEMPBAN")
typedesc = "<b>TEMP JOBBAN</b><br><font size='2'>([job])<br>([duration] minutes [(unbanned) ? "" : "(<a href=\"byond://?src=[REF(src)];[HrefToken()];dbbanedit=duration;dbbanid=[banid]\">Edit</a>))"]<br>Expires [expiration]"
if("ADMIN_PERMABAN")
typedesc = "<b>ADMIN PERMABAN</b>"
if("ADMIN_TEMPBAN")
typedesc = "<b>ADMIN TEMPBAN</b><br><font size='2'>([duration] minutes [(unbanned) ? "" : "(<a href=\"byond://?src=[REF(src)];[HrefToken()];dbbanedit=duration;dbbanid=[banid]\">Edit</a>))"]<br>Expires [expiration]</font>"
output += "<tr bgcolor='[dcolor]'>"
output += "<td align='center'>[typedesc]</td>"
output += "<td align='center'><b>[ban_key]</b></td>"
output += "<td align='center'>[bantime] (Round ID: [round_id])</td>"
output += "<td align='center'><b>[a_key]</b></td>"
output += "<td align='center'>[(unbanned) ? "" : "<b><a href=\"byond://?src=[REF(src)];[HrefToken()];dbbanedit=unban;dbbanid=[banid]\">Unban</a></b>"]</td>"
output += "</tr>"
output += "<tr bgcolor='[lcolor]'>"
output += "<td align='center' colspan='5'><b>Reason: [(unbanned) ? "" : "(<a href=\"byond://?src=[REF(src)];[HrefToken()];dbbanedit=reason;dbbanid=[banid]\">Edit</a>)"]</b> <cite>\"[reason]\"</cite></td>"
output += "</tr>"
if(edits)
output += "<tr bgcolor='[dcolor]'>"
output += "<td align='center' colspan='5'><b>EDITS</b></td>"
output += "</tr>"
output += "<tr bgcolor='[lcolor]'>"
output += "<td align='center' colspan='5'><font size='2'>[edits]</font></td>"
output += "</tr>"
if(unbanned)
output += "<tr bgcolor='[dcolor]'>"
output += "<td align='center' colspan='5' bgcolor=''><b>UNBANNED by admin [unban_key] on [unbantime]</b></td>"
output += "</tr>"
output += "<tr>"
output += "<td colspan='5' bgcolor='white'>&nbsp</td>"
output += "</tr>"
qdel(query_search_bans)
output += "</table></div>"
usr << browse(output,"window=lookupbans;size=900x500")
+201
View File
@@ -0,0 +1,201 @@
//Blocks an attempt to connect before even creating our client datum thing.
//How many new ckey matches before we revert the stickyban to it's roundstart state
//These are exclusive, so once it goes over one of these numbers, it reverts the ban
#define STICKYBAN_MAX_MATCHES 20
#define STICKYBAN_MAX_EXISTING_USER_MATCHES 5 //ie, users who were connected before the ban triggered
#define STICKYBAN_MAX_ADMIN_MATCHES 2
/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE)
if (!key || !address || !computer_id)
if(real_bans_only)
return FALSE
log_access("Failed Login (invalid data): [key] [address]-[computer_id]")
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)")
if (text2num(computer_id) == 2147483647) //this cid causes stickybans to go haywire
log_access("Failed Login (invalid cid): [key] [address]-[computer_id]")
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided an invalid Computer ID.)")
var/admin = 0
var/ckey = ckey(key)
if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey])
admin = 1
//Whitelist
if(CONFIG_GET(flag/usewhitelist))
if(!check_whitelist(ckey))
if (admin)
log_admin("The admin [key] has been allowed to bypass the whitelist")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass the whitelist</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass the whitelist</span>")
else
log_access("Failed Login: [key] - Not on whitelist")
return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server")
//Guest Checking
if(!real_bans_only && IsGuestKey(key))
if (CONFIG_GET(flag/guest_ban))
log_access("Failed Login: [key] - Guests not allowed")
return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.")
if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect())
log_access("Failed Login: [key] - Guests not allowed during panic bunker")
return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.")
//Population Cap Checking
var/extreme_popcap = CONFIG_GET(number/extreme_popcap)
if(!real_bans_only && extreme_popcap && living_player_count() >= extreme_popcap && !admin)
log_access("Failed Login: [key] - Population cap reached")
return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]")
if(CONFIG_GET(flag/ban_legacy_system))
//Ban Checking
. = CheckBan(ckey, computer_id, address )
if(.)
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching ban on [.["key"]]")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching ban on [.["key"]]</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching ban on [.["key"]]</span>")
else
log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]")
return .
else
if(!SSdbcore.Connect())
var/msg = "Ban database connection failure. Key [ckey] not checked"
log_world(msg)
message_admins(msg)
return
var/ipquery = ""
var/cidquery = ""
if(address)
ipquery = " OR ip = INET_ATON('[address]') "
if(computer_id)
cidquery = " OR computerid = '[computer_id]' "
var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_ban_check.Execute(async = TRUE))
qdel(query_ban_check)
return
while(query_ban_check.NextRow())
var/pkey = query_ban_check.item[1]
var/akey = query_ban_check.item[2]
var/reason = query_ban_check.item[3]
var/expiration = query_ban_check.item[4]
var/duration = query_ban_check.item[5]
var/bantime = query_ban_check.item[6]
var/bantype = query_ban_check.item[7]
var/banid = query_ban_check.item[8]
var/ban_round_id = query_ban_check.item[9]
if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN")
//admin bans MUST match on ckey to prevent cid-spoofing attacks
// as well as dynamic ip abuse
if (ckey(pkey) != ckey)
continue
if (admin)
if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN")
log_admin("The admin [key] is admin banned (#[banid]), and has been disallowed access")
message_admins("<span class='adminnotice'>The admin [key] is admin banned (#[banid]), and has been disallowed access</span>")
else
log_admin("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching ban on [pkey] (#[banid])</span>")
continue
var/expires = ""
if(text2num(duration) > 0)
expires = " The ban is for [duration] minutes and expires on [expiration] (server time)."
else
expires = " The is a permanent ban."
var/desc = "\nReason: You, or another user of this computer or connection ([pkey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban (BanID #[banid]) was applied by [akey] on [bantime] during round ID [ban_round_id], [expires]"
. = list("reason"="[bantype]", "desc"="[desc]")
log_access("Failed Login: [key] [computer_id] [address] - Banned (#[banid]) [.["reason"]]")
qdel(query_ban_check)
return .
qdel(query_ban_check)
var/list/ban = ..() //default pager ban stuff
if (ban)
var/bannedckey = "ERROR"
if (ban["ckey"])
bannedckey = ban["ckey"]
var/newmatch = FALSE
var/client/C = GLOB.directory[ckey]
var/cachedban = SSstickyban.cache[bannedckey]
//rogue ban in the process of being reverted.
if (cachedban && cachedban["reverting"])
return null
if (cachedban && ckey != bannedckey)
newmatch = TRUE
if (cachedban["keys"])
if (cachedban["keys"][ckey])
newmatch = FALSE
if (cachedban["matches_this_round"][ckey])
newmatch = FALSE
if (newmatch && cachedban)
var/list/newmatches = cachedban["matches_this_round"]
var/list/newmatches_connected = cachedban["existing_user_matches_this_round"]
var/list/newmatches_admin = cachedban["admin_matches_this_round"]
newmatches[ckey] = ckey
if (C)
newmatches_connected[ckey] = ckey
if (admin)
newmatches_admin[ckey] = ckey
if (\
newmatches.len > STICKYBAN_MAX_MATCHES || \
newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \
newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \
)
if (cachedban["reverting"])
return null
cachedban["reverting"] = TRUE
world.SetConfig("ban", bannedckey, null)
log_game("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state")
message_admins("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state")
//do not convert to timer.
spawn (5)
world.SetConfig("ban", bannedckey, null)
sleep(1)
world.SetConfig("ban", bannedckey, null)
cachedban["matches_this_round"] = list()
cachedban["existing_user_matches_this_round"] = list()
cachedban["admin_matches_this_round"] = list()
cachedban -= "reverting"
world.SetConfig("ban", bannedckey, list2stickyban(cachedban))
return null
//byond will not trigger isbanned() for "global" host bans,
//ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked)
//So it's safe to let admins walk thru host/sticky bans here
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
return null
if (C) //user is already connected!.
to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was reversed.")
var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n"
. = list("reason" = "Stickyban", "desc" = desc)
log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]")
return .
#undef STICKYBAN_MAX_MATCHES
#undef STICKYBAN_MAX_EXISTING_USER_MATCHES
#undef STICKYBAN_MAX_ADMIN_MATCHES
+238
View File
@@ -0,0 +1,238 @@
GLOBAL_VAR(CMinutes)
GLOBAL_DATUM(Banlist, /savefile)
GLOBAL_PROTECT(Banlist)
/proc/CheckBan(ckey, id, address)
if(!GLOB.Banlist) // if Banlist cannot be located for some reason
LoadBans() // try to load the bans
if(!GLOB.Banlist) // uh oh, can't find bans!
return 0 // ABORT ABORT ABORT
. = list()
var/appeal
var/bran = CONFIG_GET(string/banappeals)
if(bran)
appeal = "\nFor more information on your ban, or to appeal, head to <a href='[bran]'>[bran]</a>"
GLOB.Banlist.cd = "/base"
if( "[ckey][id]" in GLOB.Banlist.dir )
GLOB.Banlist.cd = "[ckey][id]"
if (GLOB.Banlist["temp"])
if (!GetExp(GLOB.Banlist["minutes"]))
ClearTempbans()
return 0
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
else
GLOB.Banlist.cd = "/base/[ckey][id]"
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: <B>PERMANENT</B>\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
.["reason"] = "ckey/id"
return .
else
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
var/matches
if( ckey == GLOB.Banlist["key"] )
matches += "ckey"
if( id == GLOB.Banlist["id"] )
if(matches)
matches += "/"
matches += "id"
if( address == GLOB.Banlist["ip"] )
if(matches)
matches += "/"
matches += "ip"
if(matches)
if(GLOB.Banlist["temp"])
if (!GetExp(GLOB.Banlist["minutes"]))
ClearTempbans()
return 0
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: <B>PERMENANT</B>\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
.["reason"] = matches
return .
return 0
/proc/UpdateTime() //No idea why i made this a proc.
GLOB.CMinutes = (world.realtime / 10) / 60
return 1
/proc/LoadBans()
if(!CONFIG_GET(flag/ban_legacy_system))
return
GLOB.Banlist = new("data/banlist.bdb")
log_admin("Loading Banlist")
if (!length(GLOB.Banlist.dir))
log_admin("Banlist is empty.")
if (!GLOB.Banlist.dir.Find("base"))
log_admin("Banlist missing base dir.")
GLOB.Banlist.dir.Add("base")
GLOB.Banlist.cd = "/base"
else if (GLOB.Banlist.dir.Find("base"))
GLOB.Banlist.cd = "/base"
ClearTempbans()
return 1
/proc/ClearTempbans()
UpdateTime()
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
if (!GLOB.Banlist["key"] || !GLOB.Banlist["id"])
RemoveBan(A)
log_admin("Invalid Ban.")
message_admins("Invalid Ban.")
continue
if (!GLOB.Banlist["temp"])
continue
if (GLOB.CMinutes >= GLOB.Banlist["minutes"])
RemoveBan(A)
return 1
/proc/AddBan(key, computerid, reason, bannedby, temp, minutes, address)
var/bantimestamp
var/ban_ckey = ckey(key)
if (temp)
UpdateTime()
bantimestamp = GLOB.CMinutes + minutes
GLOB.Banlist.cd = "/base"
if ( GLOB.Banlist.dir.Find("[ban_ckey][computerid]") )
to_chat(usr, text("<span class='danger'>Ban already exists.</span>"))
return 0
else
GLOB.Banlist.dir.Add("[ban_ckey][computerid]")
GLOB.Banlist.cd = "/base/[ban_ckey][computerid]"
WRITE_FILE(GLOB.Banlist["key"], ban_ckey)
WRITE_FILE(GLOB.Banlist["id"], computerid)
WRITE_FILE(GLOB.Banlist["ip"], address)
WRITE_FILE(GLOB.Banlist["reason"], reason)
WRITE_FILE(GLOB.Banlist["bannedby"], bannedby)
WRITE_FILE(GLOB.Banlist["temp"], temp)
WRITE_FILE(GLOB.Banlist["roundid"], GLOB.round_id)
if (temp)
WRITE_FILE(GLOB.Banlist["minutes"], bantimestamp)
if(!temp)
create_message("note", key, bannedby, "Permanently banned - [reason]", null, null, 0, 0, null, 0, 0)
else
create_message("note", key, bannedby, "Banned for [minutes] minutes - [reason]", null, null, 0, 0, null, 0, 0)
return 1
/proc/RemoveBan(foldername)
var/key
var/id
GLOB.Banlist.cd = "/base/[foldername]"
GLOB.Banlist["key"] >> key
GLOB.Banlist["id"] >> id
GLOB.Banlist.cd = "/base"
if (!GLOB.Banlist.dir.Remove(foldername))
return 0
if(!usr)
log_admin_private("Ban Expired: [key]")
message_admins("Ban Expired: [key]")
else
ban_unban_log_save("[key_name(usr)] unbanned [key]")
log_admin_private("[key_name(usr)] unbanned [key]")
message_admins("[key_name_admin(usr)] unbanned: [key]")
usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN)
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
if (key == GLOB.Banlist["key"] /*|| id == Banlist["id"]*/)
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Remove(A)
continue
return 1
/proc/GetExp(minutes as num)
UpdateTime()
var/exp = minutes - GLOB.CMinutes
if (exp <= 0)
return 0
else
var/timeleftstring
if (exp >= 1440) //1440 = 1 day in minutes
timeleftstring = "[round(exp / 1440, 0.1)] Days"
else if (exp >= 60) //60 = 1 hour in minutes
timeleftstring = "[round(exp / 60, 0.1)] Hours"
else
timeleftstring = "[exp] Minutes"
return timeleftstring
/datum/admins/proc/unbanpanel()
var/count = 0
var/dat
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
count++
GLOB.Banlist.cd = "/base/[A]"
var/ref = "[REF(src)]"
var/key = GLOB.Banlist["key"]
var/id = GLOB.Banlist["id"]
var/ip = GLOB.Banlist["ip"]
var/reason = GLOB.Banlist["reason"]
var/by = GLOB.Banlist["bannedby"]
var/expiry
if(GLOB.Banlist["temp"])
expiry = GetExp(GLOB.Banlist["minutes"])
if(!expiry)
expiry = "Removal Pending"
else
expiry = "Permaban"
dat += text("<tr><td><A href='?src=[ref];unbanf=[key][id]'>(U)</A><A href='?src=[ref];unbane=[key][id]'>(E)</A> Key: <B>[key]</B></td><td>ComputerID: <B>[id]</B></td><td>IP: <B>[ip]</B></td><td> [expiry]</td><td>(By: [by])</td><td>(Reason: [reason])</td></tr>")
dat += "</table>"
dat = "<HR><B>Bans:</B> <FONT COLOR=blue>(U) = Unban , (E) = Edit Ban</FONT> - <FONT COLOR=green>([count] Bans)</FONT><HR><table border=1 rules=all frame=void cellspacing=0 cellpadding=3 >[dat]"
usr << browse(dat, "window=unbanp;size=875x400")
//////////////////////////////////// DEBUG ////////////////////////////////////
/proc/CreateBans()
UpdateTime()
var/i
var/last
for(i=0, i<1001, i++)
var/a = pick(1,0)
var/b = pick(1,0)
if(b)
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Add("trash[i]trashid[i]")
GLOB.Banlist.cd = "/base/trash[i]trashid[i]"
WRITE_FILE(GLOB.Banlist["key"], "trash[i]")
else
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Add("[last]trashid[i]")
GLOB.Banlist.cd = "/base/[last]trashid[i]"
WRITE_FILE(GLOB.Banlist["key"], last)
WRITE_FILE(GLOB.Banlist["id"], "trashid[i]")
WRITE_FILE(GLOB.Banlist["reason"], "Trashban[i].")
WRITE_FILE(GLOB.Banlist["temp"], a)
WRITE_FILE(GLOB.Banlist["minutes"], GLOB.CMinutes + rand(1,2000))
WRITE_FILE(GLOB.Banlist["bannedby"], "trashmin")
last = "trash[i]"
GLOB.Banlist.cd = "/base"
/proc/ClearAllBans()
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
RemoveBan(A)
+916
View File
@@ -0,0 +1,916 @@
////////////////////////////////
/proc/message_admins(msg)
msg = "<span class=\"admin\"><span class=\"prefix\">ADMIN LOG:</span> <span class=\"message linkify\">[msg]</span></span>"
to_chat(GLOB.admins, msg)
/proc/relay_msg_admins(msg)
msg = "<span class=\"admin\"><span class=\"prefix\">RELAY:</span> <span class=\"message linkify\">[msg]</span></span>"
to_chat(GLOB.admins, msg)
///////////////////////////////////////////////////////////////////////////////////////////////Panels
/datum/admins/proc/show_player_panel(mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Show Player Panel"
set desc="Edit player (respawn, ban, heal, etc)"
if(!check_rights())
return
log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].")
if(!M)
to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.")
return
var/body = "<html><head><title>Options for [M.key]</title></head>"
body += "<body>Options panel for <b>[M]</b>"
if(M.client)
body += " played by <b>[M.client]</b> "
body += "\[<A href='?_src_=holder;[HrefToken()];editrights=[(GLOB.admin_datums[M.client.ckey] || GLOB.deadmins[M.client.ckey]) ? "rank" : "add"];key=[M.key]'>[M.client.holder ? M.client.holder.rank : "Player"]</A>\]"
if(CONFIG_GET(flag/use_exp_tracking))
body += "\[<A href='?_src_=holder;[HrefToken()];getplaytimewindow=[REF(M)]'>" + M.client.get_exp_living() + "</a>\]"
if(isnewplayer(M))
body += " <B>Hasn't Entered Game</B> "
else
body += " \[<A href='?_src_=holder;[HrefToken()];revive=[REF(M)]'>Heal</A>\] "
if(M.client)
body += "<br>\[<b>First Seen:</b> [M.client.player_join_date]\]\[<b>Byond account registered on:</b> [M.client.account_join_date]\]"
body += "<br><br><b>Show related accounts by:</b> "
body += "\[ <a href='?_src_=holder;[HrefToken()];showrelatedacc=cid;client=[REF(M.client)]'>CID</a> | "
body += "<a href='?_src_=holder;[HrefToken()];showrelatedacc=ip;client=[REF(M.client)]'>IP</a> \]"
var/rep = 0
rep += SSpersistence.antag_rep[M.ckey]
body += "<br><br>Antagonist reputation: [rep]"
body += "<br><a href='?_src_=holder;[HrefToken()];modantagrep=add;mob=[REF(M)]'>\[increase\]</a> "
body += "<a href='?_src_=holder;[HrefToken()];modantagrep=subtract;mob=[REF(M)]'>\[decrease\]</a> "
body += "<a href='?_src_=holder;[HrefToken()];modantagrep=set;mob=[REF(M)]'>\[set\]</a> "
body += "<a href='?_src_=holder;[HrefToken()];modantagrep=zero;mob=[REF(M)]'>\[zero\]</a>"
var/full_version = "Unknown"
if(M.client.byond_version)
full_version = "[M.client.byond_version].[M.client.byond_build ? M.client.byond_build : "xxx"]"
body += "<br>\[<b>Byond version:</b> [full_version]\]<br>"
body += "<br><br>\[ "
body += "<a href='?_src_=vars;[HrefToken()];Vars=[REF(M)]'>VV</a> - "
if(M.mind)
body += "<a href='?_src_=holder;[HrefToken()];traitor=[REF(M)]'>TP</a> - "
else
body += "<a href='?_src_=holder;[HrefToken()];initmind=[REF(M)]'>Init Mind</a> - "
if (iscyborg(M))
body += "<a href='?_src_=holder;[HrefToken()];borgpanel=[REF(M)]'>BP</a> - "
body += "<a href='?priv_msg=[M.ckey]'>PM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];subtlemessage=[REF(M)]'>SM</a> - "
if (ishuman(M) && M.mind)
body += "<a href='?_src_=holder;[HrefToken()];HeadsetMessage=[REF(M)]'>HM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(M)]'>FLW</a> - "
//Default to client logs if available
var/source = LOGSRC_MOB
if(M.client)
source = LOGSRC_CLIENT
body += "<a href='?_src_=holder;[HrefToken()];individuallog=[REF(M)];log_src=[source]'>LOGS</a>\] <br>"
body += "<b>Mob type</b> = [M.type]<br><br>"
body += "<A href='?_src_=holder;[HrefToken()];boot2=[REF(M)]'>Kick</A> | "
body += "<A href='?_src_=holder;[HrefToken()];newban=[REF(M)]'>Ban</A> | "
body += "<A href='?_src_=holder;[HrefToken()];jobban2=[REF(M)]'>Jobban</A> | "
body += "<A href='?_src_=holder;[HrefToken()];appearanceban=[REF(M)]'>Identity Ban</A> | "
var/rm = REF(M)
if(jobban_isbanned(M, "OOC"))
body+= "<A href='?_src_=holder;[HrefToken()];jobban3=OOC;jobban4=[rm]'><font color=red>OOCBan</font></A> | "
else
body+= "<A href='?_src_=holder;[HrefToken()];jobban3=OOC;jobban4=[rm]'>OOCBan</A> | "
if(QDELETED(M) || QDELETED(usr))
return
if(jobban_isbanned(M, "emote"))
body+= "<A href='?_src_=holder;[HrefToken()];jobban3=emote;jobban4=[rm]'><font color=red>EmoteBan</font></A> | "
else
body+= "<A href='?_src_=holder;[HrefToken()];jobban3=emote;jobban4=[rm]'>Emoteban</A> | "
if(QDELETED(M) || QDELETED(usr))
return
body += "<A href='?_src_=holder;[HrefToken()];showmessageckey=[M.ckey]'>Notes | Messages | Watchlist</A> | "
if(M.client)
body += "| <A href='?_src_=holder;[HrefToken()];sendtoprison=[REF(M)]'>Prison</A> | "
body += "\ <A href='?_src_=holder;[HrefToken()];sendbacktolobby=[REF(M)]'>Send back to Lobby</A> | "
var/muted = M.client.prefs.muted
body += "<br><b>Mute: </b> "
body += "\[<A href='?_src_=holder;[HrefToken()];mute=[M.ckey];mute_type=[MUTE_IC]'><font color='[(muted & MUTE_IC)?"red":"blue"]'>IC</font></a> | "
body += "<A href='?_src_=holder;[HrefToken()];mute=[M.ckey];mute_type=[MUTE_OOC]'><font color='[(muted & MUTE_OOC)?"red":"blue"]'>OOC</font></a> | "
body += "<A href='?_src_=holder;[HrefToken()];mute=[M.ckey];mute_type=[MUTE_PRAY]'><font color='[(muted & MUTE_PRAY)?"red":"blue"]'>PRAY</font></a> | "
body += "<A href='?_src_=holder;[HrefToken()];mute=[M.ckey];mute_type=[MUTE_ADMINHELP]'><font color='[(muted & MUTE_ADMINHELP)?"red":"blue"]'>ADMINHELP</font></a> | "
body += "<A href='?_src_=holder;[HrefToken()];mute=[M.ckey];mute_type=[MUTE_DEADCHAT]'><font color='[(muted & MUTE_DEADCHAT)?"red":"blue"]'>DEADCHAT</font></a>\]"
body += "(<A href='?_src_=holder;[HrefToken()];mute=[M.ckey];mute_type=[MUTE_ALL]'><font color='[(muted & MUTE_ALL)?"red":"blue"]'>toggle all</font></a>)"
body += "<br><br>"
body += "<A href='?_src_=holder;[HrefToken()];jumpto=[REF(M)]'><b>Jump to</b></A> | "
body += "<A href='?_src_=holder;[HrefToken()];getmob=[REF(M)]'>Get</A> | "
body += "<A href='?_src_=holder;[HrefToken()];sendmob=[REF(M)]'>Send To</A>"
body += "<br><br>"
body += "<A href='?_src_=holder;[HrefToken()];traitor=[REF(M)]'>Traitor panel</A> | "
body += "<A href='?_src_=holder;[HrefToken()];narrateto=[REF(M)]'>Narrate to</A> | "
body += "<A href='?_src_=holder;[HrefToken()];subtlemessage=[REF(M)]'>Subtle message</A> | "
body += "<A href='?_src_=holder;[HrefToken()];languagemenu=[REF(M)]'>Language Menu</A>"
if (M.client)
if(!isnewplayer(M))
body += "<br><br>"
body += "<b>Transformation:</b>"
body += "<br>"
//Human
if(ishuman(M))
body += "<B>Human</B> | "
else
body += "<A href='?_src_=holder;[HrefToken()];humanone=[REF(M)]'>Humanize</A> | "
//Monkey
if(ismonkey(M))
body += "<B>Monkeyized</B> | "
else
body += "<A href='?_src_=holder;[HrefToken()];monkeyone=[REF(M)]'>Monkeyize</A> | "
//Corgi
if(iscorgi(M))
body += "<B>Corgized</B> | "
else
body += "<A href='?_src_=holder;[HrefToken()];corgione=[REF(M)]'>Corgize</A> | "
//AI / Cyborg
if(isAI(M))
body += "<B>Is an AI</B> "
else if(ishuman(M))
body += "<A href='?_src_=holder;[HrefToken()];makeai=[REF(M)]'>Make AI</A> | "
body += "<A href='?_src_=holder;[HrefToken()];makerobot=[REF(M)]'>Make Robot</A> | "
body += "<A href='?_src_=holder;[HrefToken()];makealien=[REF(M)]'>Make Alien</A> | "
body += "<A href='?_src_=holder;[HrefToken()];makeslime=[REF(M)]'>Make Slime</A> | "
body += "<A href='?_src_=holder;[HrefToken()];makeblob=[REF(M)]'>Make Blob</A> | "
//Simple Animals
if(isanimal(M))
body += "<A href='?_src_=holder;[HrefToken()];makeanimal=[REF(M)]'>Re-Animalize</A> | "
else
body += "<A href='?_src_=holder;[HrefToken()];makeanimal=[REF(M)]'>Animalize</A> | "
body += "<br><br>"
body += "<b>Rudimentary transformation:</b><font size=2><br>These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.</font><br>"
body += "<A href='?_src_=holder;[HrefToken()];simplemake=observer;mob=[REF(M)]'>Observer</A> | "
body += "\[ Alien: <A href='?_src_=holder;[HrefToken()];simplemake=drone;mob=[REF(M)]'>Drone</A>, "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=hunter;mob=[REF(M)]'>Hunter</A>, "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=sentinel;mob=[REF(M)]'>Sentinel</A>, "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=praetorian;mob=[REF(M)]'>Praetorian</A>, "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=queen;mob=[REF(M)]'>Queen</A>, "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=larva;mob=[REF(M)]'>Larva</A> \] "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=human;mob=[REF(M)]'>Human</A> "
body += "\[ slime: <A href='?_src_=holder;[HrefToken()];simplemake=slime;mob=[REF(M)]'>Baby</A>, "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=adultslime;mob=[REF(M)]'>Adult</A> \] "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=monkey;mob=[REF(M)]'>Monkey</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=robot;mob=[REF(M)]'>Cyborg</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=cat;mob=[REF(M)]'>Cat</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=runtime;mob=[REF(M)]'>Runtime</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=corgi;mob=[REF(M)]'>Corgi</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=ian;mob=[REF(M)]'>Ian</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=crab;mob=[REF(M)]'>Crab</A> | "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=coffee;mob=[REF(M)]'>Coffee</A> | "
body += "\[ Construct: <A href='?_src_=holder;[HrefToken()];simplemake=constructarmored;mob=[REF(M)]'>Juggernaut</A> , "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=constructbuilder;mob=[REF(M)]'>Artificer</A> , "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=constructwraith;mob=[REF(M)]'>Wraith</A> \] "
body += "<A href='?_src_=holder;[HrefToken()];simplemake=shade;mob=[REF(M)]'>Shade</A>"
body += "<br>"
if (M.client)
body += "<br><br>"
body += "<b>Other actions:</b>"
body += "<br>"
body += "<A href='?_src_=holder;[HrefToken()];forcespeech=[REF(M)]'>Forcesay</A> | "
body += "<A href='?_src_=holder;[HrefToken()];tdome1=[REF(M)]'>Thunderdome 1</A> | "
body += "<A href='?_src_=holder;[HrefToken()];tdome2=[REF(M)]'>Thunderdome 2</A> | "
body += "<A href='?_src_=holder;[HrefToken()];tdomeadmin=[REF(M)]'>Thunderdome Admin</A> | "
body += "<A href='?_src_=holder;[HrefToken()];tdomeobserve=[REF(M)]'>Thunderdome Observer</A> | "
body += usr.client.citaPPoptions(M) // CITADEL
body += "<br>"
body += "</body></html>"
usr << browse(body, "window=adminplayeropts-[REF(M)];size=550x515")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/access_news_network() //MARKER
set category = "Fun"
set name = "Access Newscaster Network"
set desc = "Allows you to view, add and edit news feeds."
if (!istype(src, /datum/admins))
src = usr.client.holder
if (!istype(src, /datum/admins))
to_chat(usr, "Error: you are not an admin!")
return
var/dat
dat = text("<HEAD><TITLE>Admin Newscaster</TITLE></HEAD><H3>Admin Newscaster Unit</H3>")
switch(admincaster_screen)
if(0)
dat += "Welcome to the admin newscaster.<BR> Here you can add, edit and censor every newspiece on the network."
dat += "<BR>Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units."
dat += "<BR>Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things!</FONT>"
if(GLOB.news_network.wanted_issue.active)
dat+= "<HR><A href='?src=[REF(src)];[HrefToken()];ac_view_wanted=1'>Read Wanted Issue</A>"
dat+= "<HR><BR><A href='?src=[REF(src)];[HrefToken()];ac_create_channel=1'>Create Feed Channel</A>"
dat+= "<BR><A href='?src=[REF(src)];[HrefToken()];ac_view=1'>View Feed Channels</A>"
dat+= "<BR><A href='?src=[REF(src)];[HrefToken()];ac_create_feed_story=1'>Submit new Feed story</A>"
dat+= "<BR><BR><A href='?src=[REF(usr)];[HrefToken()];mach_close=newscaster_main'>Exit</A>"
var/wanted_already = 0
if(GLOB.news_network.wanted_issue.active)
wanted_already = 1
dat+="<HR><B>Feed Security functions:</B><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_menu_wanted=1'>[(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue</A>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_menu_censor_story=1'>Censor Feed Stories</A>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_menu_censor_channel=1'>Mark Feed Channel with Nanotrasen D-Notice (disables and locks the channel).</A>"
dat+="<BR><HR><A href='?src=[REF(src)];[HrefToken()];ac_set_signature=1'>The newscaster recognises you as:<BR> <FONT COLOR='green'>[src.admin_signature]</FONT></A>"
if(1)
dat+= "Station Feed Channels<HR>"
if( isemptylist(GLOB.news_network.network_channels) )
dat+="<I>No active channels found...</I>"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
if(CHANNEL.is_admin_channel)
dat+="<B><FONT style='BACKGROUND-COLOR: LightGreen'><A href='?src=[REF(src)];ac_show_channel=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A></FONT></B><BR>"
else
dat+="<B><A href='?src=[REF(src)];[HrefToken()];ac_show_channel=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A> [(CHANNEL.censored) ? ("<FONT COLOR='red'>***</FONT>") : ""]<BR></B>"
dat+="<BR><HR><A href='?src=[REF(src)];[HrefToken()];ac_refresh=1'>Refresh</A>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Back</A>"
if(2)
dat+="Creating new Feed Channel..."
dat+="<HR><B><A href='?src=[REF(src)];[HrefToken()];ac_set_channel_name=1'>Channel Name</A>:</B> [src.admincaster_feed_channel.channel_name]<BR>"
dat+="<B><A href='?src=[REF(src)];[HrefToken()];ac_set_signature=1'>Channel Author</A>:</B> <FONT COLOR='green'>[src.admin_signature]</FONT><BR>"
dat+="<B><A href='?src=[REF(src)];[HrefToken()];ac_set_channel_lock=1'>Will Accept Public Feeds</A>:</B> [(src.admincaster_feed_channel.locked) ? ("NO") : ("YES")]<BR><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_submit_new_channel=1'>Submit</A><BR><BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Cancel</A><BR>"
if(3)
dat+="Creating new Feed Message..."
dat+="<HR><B><A href='?src=[REF(src)];[HrefToken()];ac_set_channel_receiving=1'>Receiving Channel</A>:</B> [src.admincaster_feed_channel.channel_name]<BR>" //MARK
dat+="<B>Message Author:</B> <FONT COLOR='green'>[src.admin_signature]</FONT><BR>"
dat+="<B><A href='?src=[REF(src)];[HrefToken()];ac_set_new_message=1'>Message Body</A>:</B> [src.admincaster_feed_message.returnBody(-1)] <BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_submit_new_message=1'>Submit</A><BR><BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Cancel</A><BR>"
if(4)
dat+="Feed story successfully submitted to [src.admincaster_feed_channel.channel_name].<BR><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Return</A><BR>"
if(5)
dat+="Feed Channel [src.admincaster_feed_channel.channel_name] created successfully.<BR><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Return</A><BR>"
if(6)
dat+="<B><FONT COLOR='maroon'>ERROR: Could not submit Feed story to Network.</B></FONT><HR><BR>"
if(src.admincaster_feed_channel.channel_name=="")
dat+="<FONT COLOR='maroon'>•Invalid receiving channel name.</FONT><BR>"
if(src.admincaster_feed_message.returnBody(-1) == "" || src.admincaster_feed_message.returnBody(-1) == "\[REDACTED\]")
dat+="<FONT COLOR='maroon'>•Invalid message body.</FONT><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[3]'>Return</A><BR>"
if(7)
dat+="<B><FONT COLOR='maroon'>ERROR: Could not submit Feed Channel to Network.</B></FONT><HR><BR>"
if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]")
dat+="<FONT COLOR='maroon'>•Invalid channel name.</FONT><BR>"
var/check = 0
for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels)
if(FC.channel_name == src.admincaster_feed_channel.channel_name)
check = 1
break
if(check)
dat+="<FONT COLOR='maroon'>•Channel name already in use.</FONT><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[2]'>Return</A><BR>"
if(9)
dat+="<B>[admincaster_feed_channel.channel_name]: </B><FONT SIZE=1>\[created by: <FONT COLOR='maroon'>[admincaster_feed_channel.returnAuthor(-1)]</FONT>\]</FONT><HR>"
if(src.admincaster_feed_channel.censored)
dat+="<FONT COLOR='red'><B>ATTENTION: </B></FONT>This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.<BR>"
dat+="No further feed story additions are allowed while the D-Notice is in effect.</FONT><BR><BR>"
else
if( isemptylist(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
var/i = 0
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
i++
dat+="-[MESSAGE.returnBody(-1)] <BR>"
if(MESSAGE.img)
usr << browse_rsc(MESSAGE.img, "tmp_photo[i].png")
dat+="<img src='tmp_photo[i].png' width = '180'><BR><BR>"
dat+="<FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.returnAuthor(-1)]</FONT>\]</FONT><BR>"
dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:<br>"
for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments)
dat+="[comment.body]<br><font size=1>[comment.author] [comment.time_stamp]</font><br>"
dat+="<br>"
dat+="<BR><HR><A href='?src=[REF(src)];[HrefToken()];ac_refresh=1'>Refresh</A>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[1]'>Back</A>"
if(10)
dat+="<B>Nanotrasen Feed Censorship Tool</B><BR>"
dat+="<FONT SIZE=1>NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.<BR>"
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.</FONT>"
dat+="<HR>Select Feed channel to get Stories from:<BR>"
if(isemptylist(GLOB.news_network.network_channels))
dat+="<I>No feed channels found active...</I><BR>"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
dat+="<A href='?src=[REF(src)];[HrefToken()];ac_pick_censor_channel=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A> [(CHANNEL.censored) ? ("<FONT COLOR='red'>***</FONT>") : ""]<BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Cancel</A>"
if(11)
dat+="<B>Nanotrasen D-Notice Handler</B><HR>"
dat+="<FONT SIZE=1>A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.</FONT><HR>"
if(isemptylist(GLOB.news_network.network_channels))
dat+="<I>No feed channels found active...</I><BR>"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
dat+="<A href='?src=[REF(src)];[HrefToken()];ac_pick_d_notice=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A> [(CHANNEL.censored) ? ("<FONT COLOR='red'>***</FONT>") : ""]<BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Back</A>"
if(12)
dat+="<B>[src.admincaster_feed_channel.channel_name]: </B><FONT SIZE=1>\[ created by: <FONT COLOR='maroon'>[src.admincaster_feed_channel.returnAuthor(-1)]</FONT> \]</FONT><BR>"
dat+="<FONT SIZE=2><A href='?src=[REF(src)];[HrefToken()];ac_censor_channel_author=[REF(src.admincaster_feed_channel)]'>[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]</A></FONT><HR>"
if( isemptylist(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
dat+="-[MESSAGE.returnBody(-1)] <BR><FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.returnAuthor(-1)]</FONT>\]</FONT><BR>"
dat+="<FONT SIZE=2><A href='?src=[REF(src)];[HrefToken()];ac_censor_channel_story_body=[REF(MESSAGE)]'>[(MESSAGE.bodyCensor) ? ("Undo story censorship") : ("Censor story")]</A> - <A href='?src=[REF(src)];[HrefToken()];ac_censor_channel_story_author=[REF(MESSAGE)]'>[(MESSAGE.authorCensor) ? ("Undo Author Censorship") : ("Censor message Author")]</A></FONT><BR>"
dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: <a href='?src=[REF(src)];[HrefToken()];ac_lock_comment=[REF(MESSAGE)]'>[MESSAGE.locked ? "Unlock" : "Lock"]</a><br>"
for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments)
dat+="[comment.body] <a href='?src=[REF(src)];[HrefToken()];ac_del_comment=[REF(comment)];ac_del_comment_msg=[REF(MESSAGE)]'>X</a><br><font size=1>[comment.author] [comment.time_stamp]</font><br>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[10]'>Back</A>"
if(13)
dat+="<B>[src.admincaster_feed_channel.channel_name]: </B><FONT SIZE=1>\[ created by: <FONT COLOR='maroon'>[src.admincaster_feed_channel.returnAuthor(-1)]</FONT> \]</FONT><BR>"
dat+="Channel messages listed below. If you deem them dangerous to the station, you can <A href='?src=[REF(src)];[HrefToken()];ac_toggle_d_notice=[REF(src.admincaster_feed_channel)]'>Bestow a D-Notice upon the channel</A>.<HR>"
if(src.admincaster_feed_channel.censored)
dat+="<FONT COLOR='red'><B>ATTENTION: </B></FONT>This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.<BR>"
dat+="No further feed story additions are allowed while the D-Notice is in effect.</FONT><BR><BR>"
else
if( isemptylist(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
dat+="-[MESSAGE.returnBody(-1)] <BR><FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.returnAuthor(-1)]</FONT>\]</FONT><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[11]'>Back</A>"
if(14)
dat+="<B>Wanted Issue Handler:</B>"
var/wanted_already = 0
var/end_param = 1
if(GLOB.news_network.wanted_issue.active)
wanted_already = 1
end_param = 2
if(wanted_already)
dat+="<FONT SIZE=2><BR><I>A wanted issue is already in Feed Circulation. You can edit or cancel it below.</FONT></I>"
dat+="<HR>"
dat+="<A href='?src=[REF(src)];[HrefToken()];ac_set_wanted_name=1'>Criminal Name</A>: [src.admincaster_wanted_message.criminal] <BR>"
dat+="<A href='?src=[REF(src)];[HrefToken()];ac_set_wanted_desc=1'>Description</A>: [src.admincaster_wanted_message.body] <BR>"
if(wanted_already)
dat+="<B>Wanted Issue created by:</B><FONT COLOR='green'>[GLOB.news_network.wanted_issue.scannedUser]</FONT><BR>"
else
dat+="<B>Wanted Issue will be created under prosecutor:</B><FONT COLOR='green'>[src.admin_signature]</FONT><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_submit_wanted=[end_param]'>[(wanted_already) ? ("Edit Issue") : ("Submit")]</A>"
if(wanted_already)
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_cancel_wanted=1'>Take down Issue</A>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Cancel</A>"
if(15)
dat+="<FONT COLOR='green'>Wanted issue for [src.admincaster_wanted_message.criminal] is now in Network Circulation.</FONT><BR><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Return</A><BR>"
if(16)
dat+="<B><FONT COLOR='maroon'>ERROR: Wanted Issue rejected by Network.</B></FONT><HR><BR>"
if(src.admincaster_wanted_message.criminal =="" || src.admincaster_wanted_message.criminal == "\[REDACTED\]")
dat+="<FONT COLOR='maroon'>•Invalid name for person wanted.</FONT><BR>"
if(src.admincaster_wanted_message.body == "" || src.admincaster_wanted_message.body == "\[REDACTED\]")
dat+="<FONT COLOR='maroon'>•Invalid description.</FONT><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Return</A><BR>"
if(17)
dat+="<B>Wanted Issue successfully deleted from Circulation</B><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Return</A><BR>"
if(18)
dat+="<B><FONT COLOR ='maroon'>-- STATIONWIDE WANTED ISSUE --</B></FONT><BR><FONT SIZE=2>\[Submitted by: <FONT COLOR='green'>[GLOB.news_network.wanted_issue.scannedUser]</FONT>\]</FONT><HR>"
dat+="<B>Criminal</B>: [GLOB.news_network.wanted_issue.criminal]<BR>"
dat+="<B>Description</B>: [GLOB.news_network.wanted_issue.body]<BR>"
dat+="<B>Photo:</B>: "
if(GLOB.news_network.wanted_issue.img)
usr << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png")
dat+="<BR><img src='tmp_photow.png' width = '180'>"
else
dat+="None"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Back</A><BR>"
if(19)
dat+="<FONT COLOR='green'>Wanted issue for [src.admincaster_wanted_message.criminal] successfully edited.</FONT><BR><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Return</A><BR>"
else
dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com"
usr << browse(dat, "window=admincaster_main;size=400x600")
onclose(usr, "admincaster_main")
/datum/admins/proc/Game()
if(!check_rights(0))
return
var/dat = {"
<center><B>Game Panel</B></center><hr>\n
<A href='?src=[REF(src)];[HrefToken()];c_mode=1'>Change Game Mode</A><br>
"}
if(GLOB.master_mode == "secret")
dat += "<A href='?src=[REF(src)];[HrefToken()];f_secret=1'>(Force Secret Mode)</A><br>"
dat += {"
<BR>
<A href='?src=[REF(src)];[HrefToken()];create_object=1'>Create Object</A><br>
<A href='?src=[REF(src)];[HrefToken()];quick_create_object=1'>Quick Create Object</A><br>
<A href='?src=[REF(src)];[HrefToken()];create_turf=1'>Create Turf</A><br>
<A href='?src=[REF(src)];[HrefToken()];create_mob=1'>Create Mob</A><br>
"}
if(marked_datum && istype(marked_datum, /atom))
dat += "<A href='?src=[REF(src)];[HrefToken()];dupe_marked_datum=1'>Duplicate Marked Datum</A><br>"
usr << browse(dat, "window=admin2;size=210x200")
return
/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge
//i.e. buttons/verbs
/datum/admins/proc/restart()
set category = "Server"
set name = "Reboot World"
set desc="Restarts the world immediately"
if (!usr.client.holder)
return
var/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
if(world.TgsAvailable())
options += "Server Restart (Kill and restart DD)";
var/rebootconfirm
if(SSticker.admin_delay_notice)
if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") == "Yes")
rebootconfirm = TRUE
else
rebootconfirm = TRUE
if(rebootconfirm)
var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
if(result)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
switch(result)
if("Regular Restart")
SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
if("Hard Restart (No Delay, No Feeback Reason)")
to_chat(world, "World reboot - [init_by]")
world.Reboot()
if("Hardest Restart (No actions, just reboot)")
to_chat(world, "Hard world reboot - [init_by]")
world.Reboot(fast_track = TRUE)
if("Server Restart (Kill and restart DD)")
to_chat(world, "Server restart - [init_by]")
world.TgsEndProcess()
/datum/admins/proc/end_round()
set category = "Server"
set name = "End Round"
set desc = "Attempts to produce a round end report and then restart the server organically."
if (!usr.client.holder)
return
var/confirm = alert("End the round and restart the game world?", "End Round", "Yes", "Cancel")
if(confirm == "Cancel")
return
if(confirm == "Yes")
SSticker.force_ending = 1
SSblackbox.record_feedback("tally", "admin_verb", 1, "End Round") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/announce()
set category = "Special Verbs"
set name = "Announce"
set desc="Announce your desires to the world"
if(!check_rights(0))
return
var/message = input("Global message to send:", "Admin Announce", null, null) as message
if(message)
if(!check_rights(R_SERVER,0))
message = adminscrub(message,500)
to_chat(world, "<span class='adminnotice'><b>[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:</b></span>\n \t [message]")
log_admin("Announce: [key_name(usr)] : [message]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/set_admin_notice()
set category = "Special Verbs"
set name = "Set Admin Notice"
set desc ="Set an announcement that appears to everyone who joins the server. Only lasts this round"
if(!check_rights(0))
return
var/new_admin_notice = input(src,"Set a public notice for this round. Everyone who joins the server will see it.\n(Leaving it blank will delete the current notice):","Set Notice",GLOB.admin_notice) as message|null
if(new_admin_notice == null)
return
if(new_admin_notice == GLOB.admin_notice)
return
if(new_admin_notice == "")
message_admins("[key_name(usr)] removed the admin notice.")
log_admin("[key_name(usr)] removed the admin notice:\n[GLOB.admin_notice]")
else
message_admins("[key_name(usr)] set the admin notice.")
log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]")
to_chat(world, "<span class ='adminnotice'><b>Admin Notice:</b>\n \t [new_admin_notice]</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
GLOB.admin_notice = new_admin_notice
return
/datum/admins/proc/toggleooc()
set category = "Server"
set desc="Toggle dis bitch"
set name="Toggle OOC"
toggle_ooc()
log_admin("[key_name(usr)] toggled OOC.")
message_admins("[key_name_admin(usr)] toggled OOC.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle OOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleooclocal()
set category = "Server"
set desc="Toggle dat bitch"
set name="Toggle Local OOC"
toggle_looc()
log_admin("[key_name(usr)] toggled LOOC.")
message_admins("[key_name_admin(usr)] toggled LOOC.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Local OOC", "[GLOB.ooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleoocdead()
set category = "Server"
set desc="Toggle dis bitch"
set name="Toggle Dead OOC"
toggle_dooc()
log_admin("[key_name(usr)] toggled Dead OOC.")
message_admins("[key_name_admin(usr)] toggled Dead OOC.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Dead OOC", "[GLOB.dooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleaooc()
set category = "Server"
set desc="Toggle to bitch"
set name="Toggle Antag OOC"
toggle_aooc()
log_admin("[key_name(usr)] toggled Antagonist OOC.")
message_admins("[key_name_admin(usr)] toggled Antagonist OOC.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Antag OOC", "[GLOB.aooc_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/startnow()
set category = "Server"
set desc="Start the round RIGHT NOW"
set name="Start Now"
if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP)
SSticker.start_immediately = TRUE
log_admin("[usr.key] has started the game.")
var/msg = ""
if(SSticker.current_state == GAME_STATE_STARTUP)
msg = " (The server is still setting up, but the round will be \
started as soon as possible.)"
message_admins("<font color='blue'>\
[usr.key] has started the game.[msg]</font>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return 1
else
to_chat(usr, "<font color='red'>Error: Start Now: Game has already started.</font>")
return 0
/datum/admins/proc/toggleenter()
set category = "Server"
set desc="People can't enter"
set name="Toggle Entering"
GLOB.enter_allowed = !( GLOB.enter_allowed )
if (!( GLOB.enter_allowed ))
to_chat(world, "<B>New players may no longer enter the game.</B>")
else
to_chat(world, "<B>New players may now enter the game.</B>")
log_admin("[key_name(usr)] toggled new player game entering.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] toggled new player game entering.</span>")
world.update_status()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleAI()
set category = "Server"
set desc="People can't be AI"
set name="Toggle AI"
var/alai = CONFIG_GET(flag/allow_ai)
CONFIG_SET(flag/allow_ai, !alai)
if (alai)
to_chat(world, "<B>The AI job is no longer chooseable.</B>")
else
to_chat(world, "<B>The AI job is chooseable now.</B>")
log_admin("[key_name(usr)] toggled AI allowed.")
world.update_status()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleMulticam()
set category = "Server"
set desc="Turns mutlicam on and off."
set name="Toggle Multicam"
var/almcam = CONFIG_GET(flag/allow_ai_multicam)
CONFIG_SET(flag/allow_ai_multicam, !almcam)
if (almcam)
to_chat(world, "<B>The AI no longer has multicam.</B>")
for(var/i in GLOB.ai_list)
var/mob/living/silicon/ai/aiPlayer = i
if(aiPlayer.multicam_on)
aiPlayer.end_multicam()
else
to_chat(world, "<B>The AI now has multicam.</B>")
log_admin("[key_name(usr)] toggled AI multicam.")
world.update_status()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Multicam", "[!almcam ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleaban()
set category = "Server"
set desc="Respawn basically"
set name="Toggle Respawn"
var/new_nores = !CONFIG_GET(flag/norespawn)
CONFIG_SET(flag/norespawn, new_nores)
if (!new_nores)
to_chat(world, "<B>You may now respawn.</B>")
else
to_chat(world, "<B>You may no longer respawn :(</B>")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].</span>")
log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].")
world.update_status()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Respawn", "[!new_nores ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/delay()
set category = "Server"
set desc="Delay the game start"
set name="Delay pre-game"
var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null
if(SSticker.current_state > GAME_STATE_PREGAME)
return alert("Too late... The game has already started!")
if(newtime)
newtime = newtime*10
SSticker.SetTimeLeft(newtime)
if(newtime < 0)
to_chat(world, "<b>The game start has been delayed.</b>")
log_admin("[key_name(usr)] delayed the round start.")
else
to_chat(world, "<b>The game will start in [DisplayTimeText(newtime)].</b>")
SEND_SOUND(world, sound('sound/ai/attention.ogg'))
log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/unprison(mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Unprison"
if (is_centcom_level(M.z))
SSjob.SendToLateJoin(M)
message_admins("[key_name_admin(usr)] has unprisoned [key_name_admin(M)]")
log_admin("[key_name(usr)] has unprisoned [key_name(M)]")
else
alert("[M.name] is not prisoned.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Unprison") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS
/datum/admins/proc/spawn_atom(object as text)
set category = "Debug"
set desc = "(atom path) Spawn an atom"
set name = "Spawn"
if(!check_rights(R_SPAWN))
return
var/chosen = pick_closest_path(object)
if(!chosen)
return
if(ispath(chosen, /turf))
var/turf/T = get_turf(usr.loc)
T.ChangeTurf(chosen)
else
var/atom/A = new chosen(usr.loc)
A.flags_1 |= ADMIN_SPAWNED_1
log_admin("[key_name(usr)] spawned [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/spawn_cargo(object as text)
set category = "Debug"
set desc = "(atom path) Spawn a cargo crate"
set name = "Spawn Cargo"
if(!check_rights(R_SPAWN))
return
var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/datum/supply_pack)))
if(!chosen)
return
var/datum/supply_pack/S = new chosen
S.admin_spawned = TRUE
S.generate(get_turf(usr))
log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/show_traitor_panel(mob/M in GLOB.mob_list)
set category = "Admin"
set desc = "Edit mobs's memory and role"
set name = "Show Traitor Panel"
if(!istype(M))
to_chat(usr, "This can only be used on instances of type /mob")
return
if(!M.mind)
to_chat(usr, "This mob has no mind!")
return
M.mind.traitor_panel()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggletintedweldhelmets()
set category = "Debug"
set desc="Reduces view range when wearing welding helmets"
set name="Toggle tinted welding helmes"
GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh )
if (GLOB.tinted_weldhelh)
to_chat(world, "<B>The tinted_weldhelh has been enabled!</B>")
else
to_chat(world, "<B>The tinted_weldhelh has been disabled!</B>")
log_admin("[key_name(usr)] toggled tinted_weldhelh.")
message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleguests()
set category = "Server"
set desc="Guests can't enter"
set name="Toggle guests"
var/new_guest_ban = !CONFIG_GET(flag/guest_ban)
CONFIG_SET(flag/guest_ban, new_guest_ban)
if (new_guest_ban)
to_chat(world, "<B>Guests may no longer enter the game.</B>")
else
to_chat(world, "<B>Guests may now enter the game.</B>")
log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.</span>")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/output_ai_laws()
var/ai_number = 0
for(var/i in GLOB.silicon_mobs)
var/mob/living/silicon/S = i
ai_number++
if(isAI(S))
to_chat(usr, "<b>AI [key_name(S, usr)]'s laws:</b>")
else if(iscyborg(S))
var/mob/living/silicon/robot/R = S
to_chat(usr, "<b>CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:</b>")
else if (ispAI(S))
to_chat(usr, "<b>pAI [key_name(S, usr)]'s laws:</b>")
else
to_chat(usr, "<b>SOMETHING SILICON [key_name(S, usr)]'s laws:</b>")
if (S.laws == null)
to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.")
else
S.laws.show_laws(usr)
if(!ai_number)
to_chat(usr, "<b>No AIs located</b>" )
/datum/admins/proc/output_all_devil_info()
var/devil_number = 0
for(var/datum/mind/D in SSticker.mode.devils)
devil_number++
var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil)
to_chat(usr, "Devil #[devil_number]:<br><br>" + devil.printdevilinfo())
if(!devil_number)
to_chat(usr, "<b>No Devils located</b>" )
/datum/admins/proc/output_devil_info(mob/living/M)
if(is_devil(M))
var/datum/antagonist/devil/devil = M.mind.has_antag_datum(/datum/antagonist/devil)
to_chat(usr, devil.printdevilinfo())
else
to_chat(usr, "<b>[M] is not a devil.")
/datum/admins/proc/manage_free_slots()
if(!check_rights())
return
var/datum/browser/browser = new(usr, "jobmanagement", "Manage Free Slots", 520)
var/list/dat = list()
var/count = 0
if(!SSjob.initialized)
alert(usr, "You cannot manage jobs before the job subsystem is initialized!")
return
dat += "<table>"
for(var/j in SSjob.occupations)
var/datum/job/job = j
count++
var/J_title = html_encode(job.title)
var/J_opPos = html_encode(job.total_positions - (job.total_positions - job.current_positions))
var/J_totPos = html_encode(job.total_positions)
dat += "<tr><td>[J_title]:</td> <td>[J_opPos]/[job.total_positions < 0 ? " (unlimited)" : J_totPos]"
dat += "</td>"
dat += "<td>"
if(job.total_positions >= 0)
dat += "<A href='?src=[REF(src)];[HrefToken()];customjobslot=[job.title]'>Custom</A> | "
dat += "<A href='?src=[REF(src)];[HrefToken()];addjobslot=[job.title]'>Add 1</A> | "
if(job.total_positions > job.current_positions)
dat += "<A href='?src=[REF(src)];[HrefToken()];removejobslot=[job.title]'>Remove</A> | "
else
dat += "Remove | "
dat += "<A href='?src=[REF(src)];[HrefToken()];unlimitjobslot=[job.title]'>Unlimit</A></td>"
else
dat += "<A href='?src=[REF(src)];[HrefToken()];limitjobslot=[job.title]'>Limit</A></td>"
browser.height = min(100 + count * 20, 650)
browser.set_content(dat.Join())
browser.open()
/datum/admins/proc/create_or_modify_area()
set category = "Debug"
set name = "Create or modify area"
create_area(usr)
//
//
//ALL DONE
//*********************************************************************************************************
//TO-DO:
//
//
//RIP ferry snowflakes
//Kicks all the clients currently in the lobby. The second parameter (kick_only_afk) determins if an is_afk() check is ran, or if all clients are kicked
//defaults to kicking everyone (afk + non afk clients in the lobby)
//returns a list of ckeys of the kicked clients
/proc/kick_clients_in_lobby(message, kick_only_afk = 0)
var/list/kicked_client_names = list()
for(var/client/C in GLOB.clients)
if(isnewplayer(C.mob))
if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk
continue
if(message)
to_chat(C, message)
kicked_client_names.Add("[C.key]")
qdel(C)
return kicked_client_names
//returns 1 to let the dragdrop code know we are trapping this event
//returns 0 if we don't plan to trap the event
/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob)
//this is the exact two check rights checks required to edit a ckey with vv.
if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0))
return 0
if (!frommob.ckey)
return 0
var/question = ""
if (tomob.ckey)
question = "This mob already has a user ([tomob.key]) in control of it! "
question += "Are you sure you want to place [frommob.name]([frommob.key]) in control of [tomob.name]?"
var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No")
if (ask != "Yes")
return 1
if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response
return 1
tomob.ghostize(0)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].</span>")
log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control")
tomob.ckey = frommob.ckey
qdel(frommob)
return 1
/client/proc/adminGreet(logout)
if(SSticker.HasRoundStarted())
var/string
if(logout && CONFIG_GET(flag/announce_admin_logout))
string = pick(
"Admin logout: [key_name(src)]")
else if(!logout && CONFIG_GET(flag/announce_admin_login) && (prefs.toggles & ANNOUNCE_LOGIN))
string = pick(
"Admin login: [key_name(src)]")
if(string)
message_admins("[string]")
+22
View File
@@ -0,0 +1,22 @@
/atom/proc/investigate_log(message, subject)
if(!message || !subject)
return
var/F = file("[GLOB.log_directory]/[subject].html")
WRITE_FILE(F, "<small>[TIME_STAMP("hh:mm:ss", FALSE)] [REF(src)] ([x],[y],[z])</small> || [src] [message]<br>")
/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES) )
set name = "Investigate"
set category = "Admin"
if(!holder)
return
switch(subject)
if("notes, memos, watchlist")
if(!check_rights(R_ADMIN))
return
browse_messages()
else
var/F = file("[GLOB.log_directory]/[subject].html")
if(!fexists(F))
to_chat(src, "<span class='danger'>No [subject] logfile was found.</span>")
return
src << browse(F,"window=investigate[subject];size=800x300")
+306
View File
@@ -0,0 +1,306 @@
GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums
GLOBAL_PROTECT(admin_ranks)
GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt
GLOBAL_PROTECT(protected_ranks)
/datum/admin_rank
var/name = "NoRank"
var/rights = R_DEFAULT
var/exclude_rights = 0
var/include_rights = 0
var/can_edit_rights = 0
/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
QDEL_IN(src, 0)
CRASH("Admin proc call creation of admin datum")
return
name = init_name
if(!name)
qdel(src)
throw EXCEPTION("Admin rank created without name.")
return
if(init_rights)
rights = init_rights
include_rights = rights
if(init_exclude_rights)
exclude_rights = init_exclude_rights
rights &= ~exclude_rights
if(init_edit_rights)
can_edit_rights = init_edit_rights
/datum/admin_rank/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admin_rank/vv_edit_var(var_name, var_value)
return FALSE
/proc/admin_keyword_to_flag(word, previous_rights=0)
var/flag = 0
switch(ckey(word))
if("buildmode","build")
flag = R_BUILDMODE
if("admin")
flag = R_ADMIN
if("ban")
flag = R_BAN
if("fun")
flag = R_FUN
if("server")
flag = R_SERVER
if("debug")
flag = R_DEBUG
if("permissions","rights")
flag = R_PERMISSIONS
if("possess")
flag = R_POSSESS
if("stealth")
flag = R_STEALTH
if("poll")
flag = R_POLL
if("varedit")
flag = R_VAREDIT
if("everything","host","all")
flag = R_EVERYTHING
if("sound","sounds")
flag = R_SOUNDS
if("spawn","create")
flag = R_SPAWN
if("autologin", "autoadmin")
flag = R_AUTOLOGIN
if("dbranks")
flag = R_DBRANKS
if("@","prev")
flag = previous_rights
return flag
// Adds/removes rights to this admin_rank
/datum/admin_rank/proc/process_keyword(word, previous_rights=0)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/flag = admin_keyword_to_flag(word, previous_rights)
if(flag)
switch(text2ascii(word,1))
if(43)
rights |= flag //+
include_rights |= flag
if(45)
rights &= ~flag //-
exclude_rights |= flag
if(42)
can_edit_rights |= flag //*
// Checks for (keyword-formatted) rights on this admin
/datum/admins/proc/check_keyword(word)
var/flag = admin_keyword_to_flag(word)
if(flag)
return ((rank.rights & flag) == flag) //true only if right has everything in flag
/proc/sync_ranks_with_db()
set waitfor = FALSE
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return
var/list/sql_ranks = list()
for(var/datum/admin_rank/R in GLOB.protected_ranks)
var/sql_rank = sanitizeSQL(R.name)
var/sql_flags = sanitizeSQL(R.include_rights)
var/sql_exclude_flags = sanitizeSQL(R.exclude_rights)
var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights)
sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]"))
SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
//load our rank - > rights associations
/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Reload blocked: Advanced ProcCall detected.</span>")
return
GLOB.admin_ranks.Cut()
GLOB.protected_ranks.Cut()
var/previous_rights = 0
//load text from file and process each line separately
for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt"))
if(!line || findtextEx(line,"#",1,2))
continue
var/next = findtext(line, "=")
var/datum/admin_rank/R = new(ckeyEx(copytext(line, 1, next)))
if(!R)
continue
GLOB.admin_ranks += R
GLOB.protected_ranks += R
var/prev = findchar(line, "+-*", next, 0)
while(prev)
next = findchar(line, "+-*", prev + 1, 0)
R.process_keyword(copytext(line, prev, next), previous_rights)
prev = next
previous_rights = R.rights
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
if(CONFIG_GET(flag/load_legacy_ranks_only))
if(!no_update)
sync_ranks_with_db()
else
var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]")
if(!query_load_admin_ranks.Execute())
message_admins("Error loading admin ranks from database. Loading from backup.")
log_sql("Error loading admin ranks from database. Loading from backup.")
dbfail = 1
else
while(query_load_admin_ranks.NextRow())
var/skip
var/rank_name = ckeyEx(query_load_admin_ranks.item[1])
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == rank_name) //this rank was already loaded from txt override
skip = 1
break
if(!skip)
var/rank_flags = text2num(query_load_admin_ranks.item[2])
var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags)
if(!R)
continue
GLOB.admin_ranks += R
qdel(query_load_admin_ranks)
//load ranks from backup file
if(dbfail)
var/backup_file = file2text("data/admins_backup.json")
if(backup_file == null)
log_world("Unable to locate admins backup file.")
return FALSE
var/list/json = json_decode(backup_file)
for(var/J in json["ranks"])
var/skip
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == "[J]") //this rank was already loaded from txt override
skip = TRUE
if(skip)
continue
var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"])
if(!R)
continue
GLOB.admin_ranks += R
return json
#ifdef TESTING
var/msg = "Permission Sets Built:\n"
for(var/datum/admin_rank/R in GLOB.admin_ranks)
msg += "\t[R.name]"
var/rights = rights2text(R.rights,"\n\t\t")
if(rights)
msg += "\t\t[rights]\n"
testing(msg)
#endif
/proc/load_admins(no_update)
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
log_sql("Failed to connect to database while loading admins. Loading from backup.")
dbfail = 1
//clear the datums references
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
C.remove_admin_verbs()
C.holder = null
GLOB.admins.Cut()
GLOB.protected_admins.Cut()
GLOB.deadmins.Cut()
var/list/backup_file_json = load_admin_ranks(dbfail, no_update)
dbfail = backup_file_json != null
//Clear profile access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
var/list/rank_names = list()
for(var/datum/admin_rank/R in GLOB.admin_ranks)
rank_names[R.name] = R
//ckeys listed in admins.txt are always made admins before sql loading is attempted
var/list/lines = world.file2list("[global.config.directory]/admins.txt")
for(var/line in lines)
if(!length(line) || findtextEx(line, "#", 1, 2))
continue
var/list/entry = splittext(line, "=")
if(entry.len < 2)
continue
var/ckey = ckey(entry[1])
var/rank = ckeyEx(entry[2])
if(!ckey || !rank)
continue
new /datum/admins(rank_names[rank], ckey, 0, 1)
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank FROM [format_table_name("admin")] ORDER BY rank")
if(!query_load_admins.Execute())
message_admins("Error loading admins from database. Loading from backup.")
log_sql("Error loading admins from database. Loading from backup.")
dbfail = 1
else
while(query_load_admins.NextRow())
var/admin_ckey = ckey(query_load_admins.item[1])
var/admin_rank = ckeyEx(query_load_admins.item[2])
var/skip
if(rank_names[admin_rank] == null)
message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].")
skip = 1
if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey])
skip = 1
if(!skip)
new /datum/admins(rank_names[admin_rank], admin_ckey)
qdel(query_load_admins)
//load admins from backup file
if(dbfail)
if(!backup_file_json)
if(backup_file_json != null)
//already tried
return
var/backup_file = file2text("data/admins_backup.json")
if(backup_file == null)
log_world("Unable to locate admins backup file.")
return
backup_file_json = json_decode(backup_file)
for(var/J in backup_file_json["admins"])
var/skip
for(var/A in GLOB.admin_datums + GLOB.deadmins)
if(A == "[J]") //this admin was already loaded from txt override
skip = TRUE
if(skip)
continue
new /datum/admins(rank_names[ckeyEx(backup_file_json["admins"]["[J]"])], ckey("[J]"))
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
msg += "\t[ckey] - [D.rank.name]\n"
testing(msg)
#endif
return dbfail
#ifdef TESTING
/client/verb/changerank(newrank in GLOB.admin_ranks)
if(holder)
holder.rank = newrank
else
holder = new /datum/admins(newrank, ckey)
remove_admin_verbs()
holder.associate(src)
/client/verb/changerights(newrights as num)
if(holder)
holder.rank.rights = newrights
else
holder = new /datum/admins("testing", newrights, ckey)
remove_admin_verbs()
holder.associate(src)
#endif
+717
View File
@@ -0,0 +1,717 @@
//admin verb groups - They can overlap if you so wish. Only one of each verb will exist in the verbs list regardless
//the procs are cause you can't put the comments in the GLOB var define
GLOBAL_PROTECT(admin_verbs_default)
GLOBAL_LIST_INIT(admin_verbs_default, world.AVerbsDefault())
/world/proc/AVerbsDefault()
return list(
/client/proc/deadmin, /*destroys our own admin datum so we can play as a regular player*/
/client/proc/cmd_admin_say, /*admin-only ooc chat*/
/client/proc/dsay, /*talk in deadchat using our ckey/fakekey*/
/client/proc/deadchat,
/client/proc/investigate_show, /*various admintools for investigation. Such as a singulo grief-log*/
/client/proc/debug_variables, /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/
/client/proc/toggleprayers,
/client/proc/toggleadminhelpsound
)
GLOBAL_PROTECT(admin_verbs_admin)
GLOBAL_LIST_INIT(admin_verbs_admin, world.AVerbsAdmin())
/world/proc/AVerbsAdmin()
return list(
/client/proc/invisimin, /*allows our mob to go invisible/visible*/
// /datum/admins/proc/show_traitor_panel, /*interface which shows a mob's mind*/ -Removed due to rare practical use. Moved to debug verbs ~Errorage
/datum/admins/proc/show_player_panel, /*shows an interface for individual players, with various links (links require additional flags*/
/datum/verbs/menu/Admin/verb/playerpanel,
/client/proc/game_panel, /*game panel, allows to change game-mode etc*/
/client/proc/check_ai_laws, /*shows AI and borg laws*/
/datum/admins/proc/toggleooc, /*toggles ooc on/off for everyone*/
/datum/admins/proc/toggleooclocal, /*toggles looc on/off for everyone*/
/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
/datum/admins/proc/toggleaooc, /*toggles antag ooc on/off*/
/datum/admins/proc/toggleenter, /*toggles whether people can join the current game*/
/datum/admins/proc/toggleguests, /*toggles whether guests can join the current game*/
/datum/admins/proc/announce, /*priority announce something to all clients.*/
/datum/admins/proc/set_admin_notice, /*announcement all clients see when joining the server.*/
/client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/
/client/proc/toggle_view_range, /*changes how far we can see*/
/client/proc/getserverlogs, /*for accessing server logs*/
/client/proc/cmd_admin_subtle_message, /*send an message to somebody as a 'voice in their head'*/
/client/proc/cmd_admin_headset_message, /*send an message to somebody through their headset as CentCom*/
/client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/
/client/proc/cmd_admin_check_contents, /*displays the contents of an instance*/
/client/proc/centcom_podlauncher,/*Open a window to launch a Supplypod and configure it or it's contents*/
/client/proc/check_antagonists, /*shows all antags*/
/datum/admins/proc/access_news_network, /*allows access of newscasters*/
/client/proc/jumptocoord, /*we ghost and jump to a coordinate*/
/client/proc/getcurrentlogs, /*for accessing server logs for the current round*/
/client/proc/Getmob, /*teleports a mob to our location*/
/client/proc/Getkey, /*teleports a mob with a certain ckey to our location*/
// /client/proc/sendmob, /*sends a mob somewhere*/ -Removed due to it needing two sorting procs to work, which were executed every time an admin right-clicked. ~Errorage
/client/proc/jumptoarea,
/client/proc/jumptokey, /*allows us to jump to the location of a mob with a certain ckey*/
/client/proc/jumptomob, /*allows us to jump to a specific mob*/
/client/proc/jumptoturf, /*allows us to jump to a specific turf*/
/client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/
/client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcom*/
/client/proc/cmd_admin_direct_narrate, /*send text directly to a player with no padding. Useful for narratives and fluff-text*/
/client/proc/cmd_admin_world_narrate, /*sends text to all players with no padding*/
/client/proc/cmd_admin_local_narrate, /*sends text to all mobs within view of atom*/
/client/proc/cmd_admin_man_up, //CIT CHANGE - adds man up verb
/client/proc/cmd_admin_man_up_global, //CIT CHANGE - ditto
/client/proc/cmd_admin_create_centcom_report,
/client/proc/cmd_change_command_name,
/client/proc/cmd_admin_check_player_exp, /* shows players by playtime */
/client/proc/toggle_combo_hud, // toggle display of the combination pizza antag and taco sci/med/eng hud
/client/proc/toggle_AI_interact, /*toggle admin ability to interact with machines as an AI*/
/client/proc/open_shuttle_manipulator, /* Opens shuttle manipulator UI */
/client/proc/respawn_character,
/client/proc/secrets,
/client/proc/toggle_hear_radio, /*allows admins to hide all radio output*/
/client/proc/reload_admins,
/client/proc/reestablish_db_connection, /*reattempt a connection to the database*/
/client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/
/client/proc/cmd_admin_pm_panel, /*admin-pm list*/
/client/proc/panicbunker,
/client/proc/addbunkerbypass,
/client/proc/revokebunkerbypass,
/client/proc/stop_sounds,
/client/proc/hide_verbs, /*hides all our adminverbs*/
/client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/
/datum/admins/proc/open_borgopanel
)
GLOBAL_PROTECT(admin_verbs_ban)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/DB_ban_panel, /client/proc/stickybanpanel))
GLOBAL_PROTECT(admin_verbs_sounds)
GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_sound, /client/proc/set_round_end_sound))
GLOBAL_PROTECT(admin_verbs_fun)
GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/cmd_admin_dress,
/client/proc/cmd_admin_gib_self,
/client/proc/drop_bomb,
/client/proc/set_dynex_scale,
/client/proc/drop_dynex_bomb,
/client/proc/cinematic,
/client/proc/one_click_antag,
/client/proc/cmd_admin_add_freeform_ai_law,
/client/proc/object_say,
/client/proc/toggle_random_events,
/client/proc/set_ooc,
/client/proc/reset_ooc,
/client/proc/forceEvent,
/client/proc/admin_change_sec_level,
/client/proc/toggle_nuke,
/client/proc/run_weather,
/client/proc/mass_zombie_infection,
/client/proc/mass_zombie_cure,
/client/proc/polymorph_all,
/client/proc/show_tip,
/client/proc/smite,
/client/proc/admin_away,
/client/proc/roll_dices //CIT CHANGE - Adds dice verb
))
GLOBAL_PROTECT(admin_verbs_spawn)
GLOBAL_LIST_INIT(admin_verbs_spawn, list(/datum/admins/proc/spawn_atom, /datum/admins/proc/spawn_cargo, /datum/admins/proc/spawn_objasmob, /client/proc/respawn_character))
GLOBAL_PROTECT(admin_verbs_server)
GLOBAL_LIST_INIT(admin_verbs_server, world.AVerbsServer())
/world/proc/AVerbsServer()
return list(
/datum/admins/proc/startnow,
/datum/admins/proc/restart,
/datum/admins/proc/end_round,
/datum/admins/proc/delay,
/datum/admins/proc/toggleaban,
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
/datum/admins/proc/toggleMulticam,
/client/proc/cmd_admin_delete, /*delete an instance/object/mob/etc*/
/client/proc/cmd_debug_del_all,
/client/proc/toggle_random_events,
/client/proc/forcerandomrotate,
/client/proc/adminchangemap,
/client/proc/toggle_hub
)
GLOBAL_PROTECT(admin_verbs_debug)
GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug())
/world/proc/AVerbsDebug()
return list(
/client/proc/restart_controller,
/client/proc/cmd_admin_list_open_jobs,
/client/proc/Debug2,
/client/proc/cmd_debug_make_powernets,
/client/proc/cmd_debug_mob_lists,
/client/proc/cmd_admin_delete,
/client/proc/cmd_debug_del_all,
/client/proc/restart_controller,
/client/proc/enable_debug_verbs,
/client/proc/callproc,
/client/proc/callproc_datum,
/client/proc/SDQL2_query,
/client/proc/test_movable_UI,
/client/proc/test_snap_UI,
/client/proc/debugNatureMapGenerator,
/client/proc/check_bomb_impacts,
/proc/machine_upgrade,
/client/proc/populate_world,
/client/proc/get_dynex_power, //*debug verbs for dynex explosions.
/client/proc/get_dynex_range, //*debug verbs for dynex explosions.
/client/proc/set_dynex_scale,
/client/proc/cmd_display_del_log,
/client/proc/create_outfits,
/client/proc/modify_goals,
/client/proc/debug_huds,
/client/proc/map_template_load,
/client/proc/map_template_upload,
/client/proc/jump_to_ruin,
/client/proc/clear_dynamic_transit,
/client/proc/toggle_medal_disable,
/client/proc/view_runtimes,
/client/proc/pump_random_event,
/client/proc/cmd_display_init_log,
/client/proc/cmd_display_overlay_log,
/client/proc/reload_configuration,
/datum/admins/proc/create_or_modify_area,
)
GLOBAL_PROTECT(admin_verbs_possess)
GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release))
GLOBAL_PROTECT(admin_verbs_permissions)
GLOBAL_LIST_INIT(admin_verbs_permissions, list(/client/proc/edit_admin_permissions))
GLOBAL_PROTECT(admin_verbs_poll)
GLOBAL_LIST_INIT(admin_verbs_poll, list(/client/proc/create_poll))
//verbs which can be hidden - needs work
GLOBAL_PROTECT(admin_verbs_hideable)
GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/client/proc/set_ooc,
/client/proc/reset_ooc,
/client/proc/deadmin,
/datum/admins/proc/show_traitor_panel,
/datum/admins/proc/toggleenter,
/datum/admins/proc/toggleguests,
/datum/admins/proc/announce,
/datum/admins/proc/set_admin_notice,
/client/proc/admin_ghost,
/client/proc/toggle_view_range,
/client/proc/cmd_admin_subtle_message,
/client/proc/cmd_admin_headset_message,
/client/proc/cmd_admin_check_contents,
/datum/admins/proc/access_news_network,
/client/proc/admin_call_shuttle,
/client/proc/admin_cancel_shuttle,
/client/proc/cmd_admin_direct_narrate,
/client/proc/cmd_admin_world_narrate,
/client/proc/cmd_admin_local_narrate,
/client/proc/play_local_sound,
/client/proc/play_sound,
/client/proc/set_round_end_sound,
/client/proc/cmd_admin_dress,
/client/proc/cmd_admin_gib_self,
/client/proc/drop_bomb,
/client/proc/drop_dynex_bomb,
/client/proc/get_dynex_range,
/client/proc/get_dynex_power,
/client/proc/set_dynex_scale,
/client/proc/cinematic,
/client/proc/cmd_admin_add_freeform_ai_law,
/client/proc/cmd_admin_create_centcom_report,
/client/proc/cmd_change_command_name,
/client/proc/object_say,
/client/proc/toggle_random_events,
/datum/admins/proc/startnow,
/datum/admins/proc/restart,
/datum/admins/proc/delay,
/datum/admins/proc/toggleaban,
/client/proc/everyone_random,
/datum/admins/proc/toggleAI,
/client/proc/restart_controller,
/client/proc/cmd_admin_list_open_jobs,
/client/proc/callproc,
/client/proc/callproc_datum,
/client/proc/Debug2,
/client/proc/reload_admins,
/client/proc/cmd_debug_make_powernets,
/client/proc/startSinglo,
/client/proc/cmd_debug_mob_lists,
/client/proc/cmd_debug_del_all,
/client/proc/enable_debug_verbs,
/proc/possess,
/proc/release,
/client/proc/reload_admins,
/client/proc/panicbunker,
/client/proc/addbunkerbypass,
/client/proc/revokebunkerbypass,
/client/proc/admin_change_sec_level,
/client/proc/toggle_nuke,
/client/proc/cmd_display_del_log,
/client/proc/toggle_combo_hud,
/client/proc/debug_huds,
/client/proc/cmd_admin_man_up, //CIT CHANGE - adds man up verb
/client/proc/cmd_admin_man_up_global //CIT CHANGE - ditto
))
/client/proc/add_admin_verbs()
if(holder)
control_freak = CONTROL_FREAK_SKIN | CONTROL_FREAK_MACROS
var/rights = holder.rank.rights
verbs += GLOB.admin_verbs_default
if(rights & R_BUILDMODE)
verbs += /client/proc/togglebuildmodeself
if(rights & R_ADMIN)
verbs += GLOB.admin_verbs_admin
if(rights & R_BAN)
verbs += GLOB.admin_verbs_ban
if(rights & R_FUN)
verbs += GLOB.admin_verbs_fun
if(rights & R_SERVER)
verbs += GLOB.admin_verbs_server
if(rights & R_DEBUG)
verbs += GLOB.admin_verbs_debug
if(rights & R_POSSESS)
verbs += GLOB.admin_verbs_possess
if(rights & R_PERMISSIONS)
verbs += GLOB.admin_verbs_permissions
if(rights & R_STEALTH)
verbs += /client/proc/stealth
if(rights & R_ADMIN)
verbs += GLOB.admin_verbs_poll
if(rights & R_SOUNDS)
verbs += GLOB.admin_verbs_sounds
if(CONFIG_GET(string/invoke_youtubedl))
verbs += /client/proc/play_web_sound
if(rights & R_SPAWN)
verbs += GLOB.admin_verbs_spawn
/client/proc/remove_admin_verbs()
verbs.Remove(
GLOB.admin_verbs_default,
/client/proc/togglebuildmodeself,
GLOB.admin_verbs_admin,
GLOB.admin_verbs_ban,
GLOB.admin_verbs_fun,
GLOB.admin_verbs_server,
GLOB.admin_verbs_debug,
GLOB.admin_verbs_possess,
GLOB.admin_verbs_permissions,
/client/proc/stealth,
GLOB.admin_verbs_poll,
GLOB.admin_verbs_sounds,
/client/proc/play_web_sound,
GLOB.admin_verbs_spawn,
/*Debug verbs added by "show debug verbs"*/
GLOB.admin_verbs_debug_mapping,
/client/proc/disable_debug_verbs,
/client/proc/readmin
)
/client/proc/hide_most_verbs()//Allows you to keep some functionality while hiding some verbs
set name = "Adminverbs - Hide Most"
set category = "Admin"
verbs.Remove(/client/proc/hide_most_verbs, GLOB.admin_verbs_hideable)
verbs += /client/proc/show_verbs
to_chat(src, "<span class='interface'>Most of your adminverbs have been hidden.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide Most Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/proc/hide_verbs()
set name = "Adminverbs - Hide All"
set category = "Admin"
remove_admin_verbs()
verbs += /client/proc/show_verbs
to_chat(src, "<span class='interface'>Almost all of your adminverbs have been hidden.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Hide All Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/proc/show_verbs()
set name = "Adminverbs - Show"
set category = "Admin"
verbs -= /client/proc/show_verbs
add_admin_verbs()
to_chat(src, "<span class='interface'>All of your adminverbs are now visible.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Adminverbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/admin_ghost()
set category = "Admin"
set name = "Aghost"
if(!holder)
return
if(isobserver(mob))
//re-enter
var/mob/dead/observer/ghost = mob
if(!ghost.mind || !ghost.mind.current) //won't do anything if there is no body
return
if(!ghost.can_reenter_corpse)
log_admin("[key_name(usr)] re-entered corpse")
message_admins("[key_name_admin(usr)] re-entered corpse")
ghost.can_reenter_corpse = 1 //force re-entering even when otherwise not possible
ghost.reenter_corpse()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Reenter") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
else if(isnewplayer(mob))
to_chat(src, "<font color='red'>Error: Aghost: Can't admin-ghost whilst in the lobby. Join or Observe first.</font>")
else
//ghostize
log_admin("[key_name(usr)] admin ghosted.")
message_admins("[key_name_admin(usr)] admin ghosted.")
var/mob/body = mob
body.ghostize(1)
if(body && !body.key)
body.key = "@[key]" //Haaaaaaaack. But the people have spoken. If it breaks; blame adminbus
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin Ghost") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/invisimin()
set name = "Invisimin"
set category = "Admin"
set desc = "Toggles ghost-like invisibility (Don't abuse this)"
if(holder && mob)
if(mob.invisibility == INVISIBILITY_OBSERVER)
mob.invisibility = initial(mob.invisibility)
to_chat(mob, "<span class='boldannounce'>Invisimin off. Invisibility reset.</span>")
else
mob.invisibility = INVISIBILITY_OBSERVER
to_chat(mob, "<span class='adminnotice'><b>Invisimin on. You are now as invisible as a ghost.</b></span>")
/client/proc/check_antagonists()
set name = "Check Antagonists"
set category = "Admin"
if(holder)
holder.check_antagonists()
log_admin("[key_name(usr)] checked antagonists.") //for tsar~
if(!isobserver(usr) && SSticker.HasRoundStarted())
message_admins("[key_name_admin(usr)] checked antagonists.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Antagonists") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/unban_panel()
set name = "Unban Panel"
set category = "Admin"
if(holder)
if(CONFIG_GET(flag/ban_legacy_system))
holder.unbanpanel()
else
holder.DB_ban_panel()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Unban Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/game_panel()
set name = "Game Panel"
set category = "Admin"
if(holder)
holder.Game()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Game Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/secrets()
set name = "Secrets"
set category = "Admin"
if (holder)
holder.Secrets()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Secrets Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/findStealthKey(txt)
if(txt)
for(var/P in GLOB.stealthminID)
if(GLOB.stealthminID[P] == txt)
return P
txt = GLOB.stealthminID[ckey]
return txt
/client/proc/createStealthKey()
var/num = (rand(0,1000))
var/i = 0
while(i == 0)
i = 1
for(var/P in GLOB.stealthminID)
if(num == GLOB.stealthminID[P])
num++
i = 0
GLOB.stealthminID["[ckey]"] = "@[num2text(num)]"
/client/proc/stealth()
set category = "Admin"
set name = "Stealth Mode"
if(holder)
if(holder.fakekey)
holder.fakekey = null
if(isobserver(mob))
mob.invisibility = initial(mob.invisibility)
mob.alpha = initial(mob.alpha)
mob.name = initial(mob.name)
mob.mouse_opacity = initial(mob.mouse_opacity)
else
var/new_key = ckeyEx(input("Enter your desired display name.", "Fake Key", key) as text|null)
if(!new_key)
return
if(length(new_key) >= 26)
new_key = copytext(new_key, 1, 26)
holder.fakekey = new_key
createStealthKey()
if(isobserver(mob))
mob.invisibility = INVISIBILITY_MAXIMUM //JUST IN CASE
mob.alpha = 0 //JUUUUST IN CASE
mob.name = " "
mob.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
log_admin("[key_name(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]")
message_admins("[key_name_admin(usr)] has turned stealth mode [holder.fakekey ? "ON" : "OFF"]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Stealth Mode") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/drop_bomb()
set category = "Special Verbs"
set name = "Drop Bomb"
set desc = "Cause an explosion of varying strength at your location."
var/list/choices = list("Small Bomb (1, 2, 3, 3)", "Medium Bomb (2, 3, 4, 4)", "Big Bomb (3, 5, 7, 5)", "Maxcap", "Custom Bomb")
var/choice = input("What size explosion would you like to produce? NOTE: You can do all this rapidly and in an IC manner (using cruise missiles!) with the Config/Launch Supplypod verb. WARNING: These ignore the maxcap") as null|anything in choices
var/turf/epicenter = mob.loc
switch(choice)
if(null)
return 0
if("Small Bomb (1, 2, 3, 3)")
explosion(epicenter, 1, 2, 3, 3, TRUE, TRUE)
if("Medium Bomb (2, 3, 4, 4)")
explosion(epicenter, 2, 3, 4, 4, TRUE, TRUE)
if("Big Bomb (3, 5, 7, 5)")
explosion(epicenter, 3, 5, 7, 5, TRUE, TRUE)
if("Maxcap")
explosion(epicenter, GLOB.MAX_EX_DEVESTATION_RANGE, GLOB.MAX_EX_HEAVY_RANGE, GLOB.MAX_EX_LIGHT_RANGE, GLOB.MAX_EX_FLASH_RANGE)
if("Custom Bomb")
var/devastation_range = input("Devastation range (in tiles):") as null|num
if(devastation_range == null)
return
var/heavy_impact_range = input("Heavy impact range (in tiles):") as null|num
if(heavy_impact_range == null)
return
var/light_impact_range = input("Light impact range (in tiles):") as null|num
if(light_impact_range == null)
return
var/flash_range = input("Flash range (in tiles):") as null|num
if(flash_range == null)
return
if(devastation_range > GLOB.MAX_EX_DEVESTATION_RANGE || heavy_impact_range > GLOB.MAX_EX_HEAVY_RANGE || light_impact_range > GLOB.MAX_EX_LIGHT_RANGE || flash_range > GLOB.MAX_EX_FLASH_RANGE)
if(alert("Bomb is bigger than the maxcap. Continue?",,"Yes","No") != "Yes")
return
epicenter = mob.loc //We need to reupdate as they may have moved again
explosion(epicenter, devastation_range, heavy_impact_range, light_impact_range, flash_range, TRUE, TRUE)
message_admins("[ADMIN_LOOKUPFLW(usr)] creating an admin explosion at [epicenter.loc].")
log_admin("[key_name(usr)] created an admin explosion at [epicenter.loc].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Bomb") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/drop_dynex_bomb()
set category = "Special Verbs"
set name = "Drop DynEx Bomb"
set desc = "Cause an explosion of varying strength at your location."
var/ex_power = input("Explosive Power:") as null|num
var/turf/epicenter = mob.loc
if(ex_power && epicenter)
dyn_explosion(epicenter, ex_power)
message_admins("[ADMIN_LOOKUPFLW(usr)] creating an admin explosion at [epicenter.loc].")
log_admin("[key_name(usr)] created an admin explosion at [epicenter.loc].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Drop Dynamic Bomb") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/get_dynex_range()
set category = "Debug"
set name = "Get DynEx Range"
set desc = "Get the estimated range of a bomb, using explosive power."
var/ex_power = input("Explosive Power:") as null|num
if (isnull(ex_power))
return
var/range = round((2 * ex_power)**GLOB.DYN_EX_SCALE)
to_chat(usr, "Estimated Explosive Range: (Devastation: [round(range*0.25)], Heavy: [round(range*0.5)], Light: [round(range)])")
/client/proc/get_dynex_power()
set category = "Debug"
set name = "Get DynEx Power"
set desc = "Get the estimated required power of a bomb, to reach a specific range."
var/ex_range = input("Light Explosion Range:") as null|num
if (isnull(ex_range))
return
var/power = (0.5 * ex_range)**(1/GLOB.DYN_EX_SCALE)
to_chat(usr, "Estimated Explosive Power: [power]")
/client/proc/set_dynex_scale()
set category = "Debug"
set name = "Set DynEx Scale"
set desc = "Set the scale multiplier of dynex explosions. The default is 0.5."
var/ex_scale = input("New DynEx Scale:") as null|num
if(!ex_scale)
return
GLOB.DYN_EX_SCALE = ex_scale
log_admin("[key_name(usr)] has modified Dynamic Explosion Scale: [ex_scale]")
message_admins("[key_name_admin(usr)] has modified Dynamic Explosion Scale: [ex_scale]")
/client/proc/give_spell(mob/T in GLOB.mob_list)
set category = "Fun"
set name = "Give Spell"
set desc = "Gives a spell to a mob."
var/list/spell_list = list()
var/type_length = length("/obj/effect/proc_holder/spell") + 2
for(var/A in GLOB.spells)
spell_list[copytext("[A]", type_length)] = A
var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in spell_list
if(!S)
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] gave [key_name(T)] the spell [S].</span>")
S = spell_list[S]
if(T.mind)
T.mind.AddSpell(new S)
else
T.AddSpell(new S)
message_admins("<span class='danger'>Spells given to mindless mobs will not be transferred in mindswap or cloning!</span>")
/client/proc/remove_spell(mob/T in GLOB.mob_list)
set category = "Fun"
set name = "Remove Spell"
set desc = "Remove a spell from the selected mob."
if(T && T.mind)
var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in T.mind.spell_list
if(S)
T.mind.RemoveSpell(S)
log_admin("[key_name(usr)] removed the spell [S] from [key_name(T)].")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] removed the spell [S] from [key_name(T)].</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/give_disease(mob/living/T in GLOB.mob_living_list)
set category = "Fun"
set name = "Give Disease"
set desc = "Gives a Disease to a mob."
if(!istype(T))
to_chat(src, "<span class='notice'>You can only give a disease to a mob of type /mob/living.</span>")
return
var/datum/disease/D = input("Choose the disease to give to that guy", "ACHOO") as null|anything in SSdisease.diseases
if(!D)
return
T.ForceContractDisease(new D, FALSE, TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Disease") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] gave [key_name(T)] the disease [D].")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] gave [key_name(T)] the disease [D].</span>")
/client/proc/object_say(obj/O in world)
set category = "Special Verbs"
set name = "OSay"
set desc = "Makes an object say something."
var/message = input(usr, "What do you want the message to be?", "Make Sound") as text | null
if(!message)
return
O.say(message)
log_admin("[key_name(usr)] made [O] at [AREACOORD(O)] say \"[message]\"")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] made [O] at [AREACOORD(O)]. say \"[message]\"</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Object Say") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/togglebuildmodeself()
set name = "Toggle Build Mode Self"
set category = "Special Verbs"
if (!(holder.rank.rights & R_BUILDMODE))
return
if(src.mob)
togglebuildmode(src.mob)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Build Mode") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/check_ai_laws()
set name = "Check AI Laws"
set category = "Admin"
if(holder)
src.holder.output_ai_laws()
/client/proc/deadmin()
set name = "Deadmin"
set category = "Admin"
set desc = "Shed your admin powers."
if(!holder)
return
if(has_antag_hud())
toggle_combo_hud()
holder.deactivate()
to_chat(src, "<span class='interface'>You are now a normal player.</span>")
log_admin("[src] deadmined themself.")
message_admins("[src] deadmined themself.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Deadmin")
/client/proc/readmin()
set name = "Readmin"
set category = "Admin"
set desc = "Regain your admin powers."
var/datum/admins/A = GLOB.deadmins[ckey]
if(!A)
A = GLOB.admin_datums[ckey]
if (!A)
var/msg = " is trying to readmin but they have no deadmin entry"
message_admins("[key_name_admin(src)][msg]")
log_admin_private("[key_name(src)][msg]")
return
A.associate(src)
if (!holder)
return //This can happen if an admin attempts to vv themself into somebody elses's deadmin datum by getting ref via brute force
to_chat(src, "<span class='interface'>You are now an admin.</span>")
message_admins("[src] re-adminned themselves.")
log_admin("[src] re-adminned themselves.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Readmin")
/client/proc/populate_world(amount = 50 as num)
set name = "Populate World"
set category = "Debug"
set desc = "(\"Amount of mobs to create\") Populate the world with test mobs."
if (amount > 0)
var/area/area
var/list/candidates
var/turf/open/floor/tile
var/j,k
for (var/i = 1 to amount)
j = 100
do
area = pick(GLOB.the_station_areas)
if (area)
candidates = get_area_turfs(area)
if (candidates.len)
k = 100
do
tile = pick(candidates)
while ((!tile || !istype(tile)) && --k > 0)
if (tile)
var/mob/living/carbon/human/hooman = new(tile)
hooman.equipOutfit(pick(subtypesof(/datum/outfit)))
testing("Spawned test mob at [COORD(tile)]")
while (!area && --j > 0)
/client/proc/toggle_AI_interact()
set name = "Toggle Admin AI Interact"
set category = "Admin"
set desc = "Allows you to interact with most machines as an AI would as a ghost"
AI_Interact = !AI_Interact
if(mob && IsAdminGhost(mob))
mob.has_unlimited_silicon_privilege = AI_Interact
log_admin("[key_name(usr)] has [AI_Interact ? "activated" : "deactivated"] Admin AI Interact")
message_admins("[key_name_admin(usr)] has [AI_Interact ? "activated" : "deactivated"] their AI interaction")
+11
View File
@@ -0,0 +1,11 @@
/datum/verbs/menu/Admin/Generate_list(client/C)
if (C.holder)
. = ..()
/datum/verbs/menu/Admin/verb/playerpanel()
set name = "Player Panel"
set desc = "Player Panel"
set category = "Admin"
if(usr.client.holder)
usr.client.holder.player_panel_new()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Player Panel New") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+217
View File
@@ -0,0 +1,217 @@
GLOBAL_VAR(antag_prototypes)
//Things to do somewhere in the future (If you're reading this feel free to do any of these)
//Add HrefTokens to these
//Make this template or at least remove + "<br>" with joins where you can grasp the big picture.
//Span classes for the headers, wrap sections in div's and style them.
//Move common admin commands to /mob (maybe integrate with vv dropdown so the list is one thing with some flag where to show it)
//Move objective initialization/editing stuff from mind to objectives and completely remove mind.objectives
/proc/cmp_antagpanel(datum/antagonist/A,datum/antagonist/B)
var/a_cat = initial(A.antagpanel_category)
var/b_cat = initial(B.antagpanel_category)
if(!a_cat && !b_cat)
return sorttext(initial(A.name),initial(B.name))
return sorttext(b_cat,a_cat)
/datum/mind/proc/add_antag_wrapper(antag_type,mob/user)
var/datum/antagonist/new_antag = new antag_type()
new_antag.admin_add(src,user)
//If something gone wrong/admin-add assign another antagonist due to whatever clean it up
if(!new_antag.owner)
qdel(new_antag)
/proc/listtrim(list/L)
for(var/x in L)
if(istext(x) && !x)
L -= x
return L
/datum/antagonist/proc/antag_panel()
var/list/commands = list()
for(var/command in get_admin_commands())
commands += "<a href='?src=[REF(src)];command=[command]'>[command]</a>"
var/command_part = commands.Join(" | ")
var/data_part = antag_panel_data()
var/objective_part = antag_panel_objectives()
var/memory_part = antag_panel_memory()
var/list/parts = listtrim(list(command_part,data_part,objective_part,memory_part))
return parts.Join("<br>")
/datum/antagonist/proc/antag_panel_objectives()
var/result = "<i><b>Objectives</b></i>:<br>"
if (objectives.len == 0)
result += "EMPTY<br>"
else
var/obj_count = 1
for(var/datum/objective/objective in objectives)
result += "<B>[obj_count]</B>: [objective.explanation_text] <a href='?src=[REF(owner)];obj_edit=[REF(objective)]'>Edit</a> <a href='?src=[REF(owner)];obj_delete=[REF(objective)]'>Delete</a> <a href='?src=[REF(owner)];obj_completed=[REF(objective)]'><font color=[objective.completed ? "green" : "red"]>[objective.completed ? "Mark as incomplete" : "Mark as complete"]</font></a><br>"
obj_count++
result += "<a href='?src=[REF(owner)];obj_add=1;target_antag=[REF(src)]'>Add objective</a><br>"
result += "<a href='?src=[REF(owner)];obj_announce=1'>Announce objectives</a><br>"
return result
/datum/antagonist/proc/antag_panel_memory()
var/out = "<b>Memory:</b><br>"
out += antag_memory
out += "<br><a href='?src=[REF(src)];memory_edit=1'>Edit memory</a><br>"
return out
/datum/mind/proc/get_common_admin_commands()
var/common_commands = "<span>Common Commands:</span>"
if(ishuman(current))
common_commands += "<a href='?src=[REF(src)];common=undress'>undress</a>"
else if(iscyborg(current))
var/mob/living/silicon/robot/R = current
if(R.emagged)
common_commands += "<a href='?src=[REF(src)];silicon=Unemag'>Unemag</a>"
else if(isAI(current))
var/mob/living/silicon/ai/A = current
if (A.connected_robots.len)
for (var/mob/living/silicon/robot/R in A.connected_robots)
if (R.emagged)
common_commands += "<a href='?src=[REF(src)];silicon=unemagcyborgs'>Unemag slaved cyborgs</a>"
break
return common_commands
/datum/mind/proc/get_special_statuses()
var/list/result = list()
if(!current)
result += "<span class='bad'>No body!</span>"
if(current && HAS_TRAIT(current, TRAIT_MINDSHIELD))
result += "<span class='good'>Mindshielded</span>"
//Move these to mob
if(iscyborg(current))
var/mob/living/silicon/robot/robot = current
if (robot.emagged)
result += "<span class='bad'>Emagged</span>"
return result.Join(" | ")
/datum/mind/proc/traitor_panel()
if(!SSticker.HasRoundStarted())
alert("Not before round-start!", "Alert")
return
if(QDELETED(src))
alert("This mind doesn't have a mob, or is deleted! For some reason!", "Edit Memory")
return
var/out = "<B>[name]</B>[(current && (current.real_name!=name))?" (as [current.real_name])":""]<br>"
out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"]<br>"
out += "Assigned role: [assigned_role]. <a href='?src=[REF(src)];role_edit=1'>Edit</a><br>"
out += "Faction and special role: <b><font color='red'>[special_role]</font></b><br>"
var/special_statuses = get_special_statuses()
if(length(special_statuses))
out += get_special_statuses() + "<br>"
if(!GLOB.antag_prototypes)
GLOB.antag_prototypes = list()
for(var/antag_type in subtypesof(/datum/antagonist))
var/datum/antagonist/A = new antag_type
var/cat_id = A.antagpanel_category
if(!GLOB.antag_prototypes[cat_id])
GLOB.antag_prototypes[cat_id] = list(A)
else
GLOB.antag_prototypes[cat_id] += A
sortTim(GLOB.antag_prototypes,/proc/cmp_text_asc,associative=TRUE)
var/list/sections = list()
var/list/priority_sections = list()
for(var/antag_category in GLOB.antag_prototypes)
var/category_header = "<i><b>[antag_category]:</b></i>"
var/list/antag_header_parts = list(category_header)
var/datum/antagonist/current_antag
var/list/possible_admin_antags = list()
for(var/datum/antagonist/prototype in GLOB.antag_prototypes[antag_category])
var/datum/antagonist/A = has_antag_datum(prototype.type)
if(A)
//We got the antag
if(!current_antag)
current_antag = A
else
continue //Let's skip subtypes of what we already shown.
else if(prototype.show_in_antagpanel)
if(prototype.can_be_owned(src))
possible_admin_antags += "<a href='?src=[REF(src)];add_antag=[prototype.type]' title='[prototype.type]'>[prototype.name]</a>"
else
possible_admin_antags += "<a class='linkOff'>[prototype.name]</a>"
else
//We don't have it and it shouldn't be shown as an option to be added.
continue
if(!current_antag) //Show antagging options
if(possible_admin_antags.len)
antag_header_parts += "<span class='highlight'>None</span>"
antag_header_parts += possible_admin_antags
else
//If there's no antags to show in this category skip the section completely
continue
else //Show removal and current one
priority_sections |= antag_category
antag_header_parts += "<span class='bad'>[current_antag.name]</span>"
antag_header_parts += "<a href='?src=[REF(src)];remove_antag=[REF(current_antag)]'>Remove</a>"
//We aren't antag of this category, grab first prototype to check the prefs (This is pretty vague but really not sure how else to do this)
var/datum/antagonist/pref_source = current_antag
if(!pref_source)
for(var/datum/antagonist/prototype in GLOB.antag_prototypes[antag_category])
if(!prototype.show_in_antagpanel)
continue
pref_source = prototype
break
if(pref_source.job_rank)
antag_header_parts += pref_source.enabled_in_preferences(src) ? "Enabled in Prefs" : "Disabled in Prefs"
//Traitor : None | Traitor | IAA
// Command1 | Command2 | Command3
// Secret Word : Banana
// Objectives:
// 1.Do the thing [a][b]
// [a][b]
// Memory:
// Uplink Code: 777 Alpha
var/cat_section = antag_header_parts.Join(" | ") + "<br>"
if(current_antag)
cat_section += current_antag.antag_panel()
sections[antag_category] = cat_section
for(var/s in priority_sections)
out += sections[s]
for(var/s in sections - priority_sections)
out += sections[s]
out += "<br>"
//Uplink
if(ishuman(current))
var/uplink_info = "<i><b>Uplink</b></i>:"
var/datum/component/uplink/U = find_syndicate_uplink()
if(U)
uplink_info += "<a href='?src=[REF(src)];common=takeuplink'>take</a>"
if (check_rights(R_FUN, 0))
uplink_info += ", <a href='?src=[REF(src)];common=crystals'>[U.telecrystals]</a> TC"
else
uplink_info += ", [U.telecrystals] TC"
else
uplink_info += "<a href='?src=[REF(src)];common=uplink'>give</a>"
uplink_info += "." //hiel grammar
out += uplink_info + "<br>"
//Common Memory
var/common_memory = "<span>Common Memory:</span>"
common_memory += memory
common_memory += "<a href='?src=[REF(src)];memory_edit=1'>Edit Memory</a>"
out += common_memory + "<br>"
//Other stuff
out += get_common_admin_commands()
var/datum/browser/panel = new(usr, "traitorpanel", "", 600, 600)
panel.set_content(out)
panel.open()
return
+40
View File
@@ -0,0 +1,40 @@
//returns a reason if M is banned from rank, returns FALSE otherwise
/proc/jobban_isbanned(mob/M, rank)
if(!M || !istype(M) || !M.ckey)
return FALSE
if(!M.client) //no cache. fallback to a datum/DBQuery
var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'")
if(!query_jobban_check_ban.warn_execute())
qdel(query_jobban_check_ban)
return
if(query_jobban_check_ban.NextRow())
var/reason = query_jobban_check_ban.item[1]
qdel(query_jobban_check_ban)
return reason ? reason : TRUE //we don't want to return "" if there is no ban reason, as that would evaluate to false
qdel(query_jobban_check_ban)
return FALSE
if(!M.client.jobbancache)
jobban_buildcache(M.client)
if(rank in M.client.jobbancache)
var/reason = M.client.jobbancache[rank]
return (reason) ? reason : TRUE //see above for why we need to do this
return FALSE
/proc/jobban_buildcache(client/C)
if(!SSdbcore.Connect())
return
if(C && istype(C))
C.jobbancache = list()
var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_jobban_build_cache.warn_execute())
qdel(query_jobban_build_cache)
return
while(query_jobban_build_cache.NextRow())
C.jobbancache[query_jobban_build_cache.item[1]] = query_jobban_build_cache.item[2]
qdel(query_jobban_build_cache)
/proc/ban_unban_log_save(var/formatted_log)
text2file(formatted_log,"data/ban_unban_log.txt")
+143
View File
@@ -0,0 +1,143 @@
#define IRC_STATUS_THROTTLE 5
/datum/tgs_chat_command/ircstatus
name = "status"
help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server"
admin_only = TRUE
var/last_irc_status = 0
/datum/tgs_chat_command/ircstatus/Run(datum/tgs_chat_user/sender, params)
var/rtod = REALTIMEOFDAY
if(rtod - last_irc_status < IRC_STATUS_THROTTLE)
return
last_irc_status = rtod
var/list/adm = get_admin_counts()
var/list/allmins = adm["total"]
var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). "
status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]."
return status
/datum/tgs_chat_command/irccheck
name = "check"
help_text = "Gets the playercount, gamemode, and address of the server"
var/last_irc_check = 0
/datum/tgs_chat_command/irccheck/Run(datum/tgs_chat_user/sender, params)
var/rtod = REALTIMEOFDAY
if(rtod - last_irc_check < IRC_STATUS_THROTTLE)
return
last_irc_check = rtod
var/server = CONFIG_GET(string/server)
return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]"
//CIT CHANGE obfuscates the gamemode for TGS bot commands on discord by removing Mode:[GLOB.master_mode]
/datum/tgs_chat_command/ahelp
name = "ahelp"
help_text = "<ckey|ticket #> <message|ticket <close|resolve|icissue|reject|reopen <ticket #>|list>>"
admin_only = TRUE
/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params)
var/list/all_params = splittext(params, " ")
if(all_params.len < 2)
return "Insufficient parameters"
var/target = all_params[1]
all_params.Cut(1, 2)
var/id = text2num(target)
if(id != null)
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id)
if(AH)
target = AH.initiator_ckey
else
return "Ticket #[id] not found!"
var/res = IrcPm(target, all_params.Join(" "), sender.friendly_name)
if(res != "Message Successful")
return res
/datum/tgs_chat_command/namecheck
name = "namecheck"
help_text = "Returns info on the specified target"
admin_only = TRUE
/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params)
params = trim(params)
if(!params)
return "Insufficient parameters"
log_admin("Chat Name Check: [sender.friendly_name] on [params]")
message_admins("Name checking [params] from [sender.friendly_name]")
return keywords_lookup(params, 1)
/datum/tgs_chat_command/adminwho
name = "adminwho"
help_text = "Lists administrators currently on the server"
admin_only = TRUE
/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params)
return ircadminwho()
GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/notify
name = "notify"
help_text = "Pings the invoker when the round ends"
admin_only = FALSE
/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params)
if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted())
return "[sender.mention], the round has already ended!"
LAZYINITLIST(GLOB.round_end_notifiees)
GLOB.round_end_notifiees["<@[sender.mention]>"] = TRUE
return "I will notify [sender.mention] when the round ends."
/datum/tgs_chat_command/sdql
name = "sdql"
help_text = "Runs an SDQL query"
admin_only = TRUE
/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params)
if(GLOB.AdminProcCaller)
return "Unable to run query, another admin proc call is in progress. Try again later."
GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin
var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller)
GLOB.AdminProcCaller = null
if(!results)
return "Query produced no output"
var/list/text_res = results.Copy(1, 3)
var/list/refs = results.len > 3 ? results.Copy(4) : null
if(refs)
var/list/L = list()
for(var/ref in refs)
var/atom/A = locate(ref)
if(A)
L += "[A]"
else
L += "[ref]"
refs = L
. = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]"
/datum/tgs_chat_command/reload_admins
name = "reload_admins"
help_text = "Forces the server to reload admins."
admin_only = TRUE
/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params)
ReloadAsync()
log_admin("[sender.friendly_name] reloaded admins via chat command.")
return "Admins reloaded."
/datum/tgs_chat_command/reload_admins/proc/ReloadAsync()
set waitfor = FALSE
load_admins()
/datum/tgs_chat_command/addbunkerbypass
name = "whitelist"
help_text = "whitelist <ckey>"
admin_only = TRUE
/datum/tgs_chat_command/addbunkerbypass/Run(datum/tgs_chat_user/sender, params)
if(!CONFIG_GET(flag/sql_enabled))
return "The Database is not enabled!"
GLOB.bunker_passthrough |= ckey(params)
log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
return "[params] has been added to the current round's bunker bypass list."
+215
View File
@@ -0,0 +1,215 @@
//I wish we had interfaces sigh, and i'm not sure giving team and antag common root is a better solution here
//Name shown on antag list
/datum/antagonist/proc/antag_listing_name()
if(!owner)
return "Unassigned"
if(owner.current)
return "<a href='?_src_=holder;[HrefToken()];adminplayeropts=[REF(owner.current)]'>[owner.current.real_name]</a> "
else
return "<a href='?_src_=vars;[HrefToken()];Vars=[REF(owner)]'>[owner.name]</a> "
//Whatever interesting things happened to the antag admins should know about
//Include additional information about antag in this part
/datum/antagonist/proc/antag_listing_status()
if(!owner)
return "(Unassigned)"
if(!owner.current)
return "<font color=red>(Body destroyed)</font>"
else
if(owner.current.stat == DEAD)
return "<font color=red>(DEAD)</font>"
else if(!owner.current.client)
return "(No client)"
//Builds the common FLW PM TP commands part
//Probably not going to be overwritten by anything but you never know
/datum/antagonist/proc/antag_listing_commands()
if(!owner)
return
var/list/parts = list()
parts += "<a href='?priv_msg=[ckey(owner.key)]'>PM</a>"
if(owner.current) //There's body to follow
parts += "<a href='?_src_=holder;[HrefToken()];adminplayerobservefollow=[REF(owner.current)]'>FLW</a>"
else
parts += ""
parts += "<a href='?_src_=holder;[HrefToken()];traitor=[REF(owner)]'>Show Objective</a>"
return parts //Better as one cell or two/three
//Builds table row for the antag
// Jim (Status) FLW PM TP
/datum/antagonist/proc/antag_listing_entry()
var/list/parts = list()
if(show_name_in_check_antagonists)
parts += "[antag_listing_name()]([name])"
else
parts += antag_listing_name()
parts += antag_listing_status()
parts += antag_listing_commands()
return "<tr><td>[parts.Join("</td><td>")]</td></tr>"
/datum/team/proc/get_team_antags(antag_type,specific = FALSE)
. = list()
for(var/datum/antagonist/A in GLOB.antagonists)
if(A.get_team() == src && (!antag_type || !specific && istype(A,antag_type) || specific && A.type == antag_type))
. += A
//Builds section for the team
/datum/team/proc/antag_listing_entry()
//NukeOps:
// Jim (Status) FLW PM TP
// Joe (Status) FLW PM TP
//Disk:
// Deep Space FLW
var/list/parts = list()
parts += "<b>[antag_listing_name()]</b><br>"
parts += "<table cellspacing=5>"
for(var/datum/antagonist/A in get_team_antags())
parts += A.antag_listing_entry()
parts += "</table>"
parts += antag_listing_footer()
return parts.Join()
/datum/team/proc/antag_listing_name()
return name
/datum/team/proc/antag_listing_footer()
return
//Moves them to the top of the list if TRUE
/datum/antagonist/proc/is_gamemode_hero()
return FALSE
/datum/team/proc/is_gamemode_hero()
return FALSE
/datum/admins/proc/build_antag_listing()
var/list/sections = list()
var/list/priority_sections = list()
var/list/all_teams = list()
var/list/all_antagonists = list()
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
continue
all_teams |= A.get_team()
all_antagonists += A
for(var/datum/team/T in all_teams)
for(var/datum/antagonist/X in all_antagonists)
if(X.get_team() == T)
all_antagonists -= X
if(T.is_gamemode_hero())
priority_sections += T.antag_listing_entry()
else
sections += T.antag_listing_entry()
sortTim(all_antagonists, /proc/cmp_antag_category)
var/current_category
var/list/current_section = list()
for(var/i in 1 to all_antagonists.len)
var/datum/antagonist/current_antag = all_antagonists[i]
var/datum/antagonist/next_antag
if(i < all_antagonists.len)
next_antag = all_antagonists[i+1]
if(!current_category)
current_category = current_antag.roundend_category
current_section += "<b>[capitalize(current_category)]</b><br>"
current_section += "<table cellspacing=5>"
current_section += current_antag.antag_listing_entry() // Name - (Traitor) - FLW | PM | TP
if(!next_antag || next_antag.roundend_category != current_antag.roundend_category) //End of section
current_section += "</table>"
if(current_antag.is_gamemode_hero())
priority_sections += current_section.Join()
else
sections += current_section.Join()
current_section.Cut()
current_category = null
var/list/all_sections = priority_sections + sections
return all_sections.Join("<br>")
/datum/admins/proc/check_antagonists()
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
return
var/list/dat = list("<html><head><title>Round Status</title></head><body><h1><B>Round Status</B></h1>")
if(SSticker.mode.replacementmode)
dat += "Former Game Mode: <B>[SSticker.mode.name]</B><BR>"
dat += "Replacement Game Mode: <B>[SSticker.mode.replacementmode.name]</B><BR>"
else
dat += "Current Game Mode: <B>[SSticker.mode.name]</B><BR>"
dat += "Round Duration: <B>[DisplayTimeText(world.time - SSticker.round_start_time)]</B><BR>"
dat += "<B>Emergency shuttle</B><BR>"
if(EMERGENCY_IDLE_OR_RECALLED)
dat += "<a href='?_src_=holder;[HrefToken()];call_shuttle=1'>Call Shuttle</a><br>"
else
var/timeleft = SSshuttle.emergency.timeLeft()
if(SSshuttle.emergency.mode == SHUTTLE_CALL)
dat += "ETA: <a href='?_src_=holder;[HrefToken()];edit_shuttle_time=1'>[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]</a><BR>"
dat += "<a href='?_src_=holder;[HrefToken()];call_shuttle=2'>Send Back</a><br>"
else
dat += "ETA: <a href='?_src_=holder;[HrefToken()];edit_shuttle_time=1'>[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]</a><BR>"
dat += "<B>Continuous Round Status</B><BR>"
dat += "<a href='?_src_=holder;[HrefToken()];toggle_continuous=1'>[CONFIG_GET(keyed_list/continuous)[SSticker.mode.config_tag] ? "Continue if antagonists die" : "End on antagonist death"]</a>"
if(CONFIG_GET(keyed_list/continuous)[SSticker.mode.config_tag])
dat += ", <a href='?_src_=holder;[HrefToken()];toggle_midround_antag=1'>[CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag] ? "creating replacement antagonists" : "not creating new antagonists"]</a><BR>"
else
dat += "<BR>"
if(CONFIG_GET(keyed_list/midround_antag)[SSticker.mode.config_tag])
dat += "Time limit: <a href='?_src_=holder;[HrefToken()];alter_midround_time_limit=1'>[CONFIG_GET(number/midround_antag_time_check)] minutes into round</a><BR>"
dat += "Living crew limit: <a href='?_src_=holder;[HrefToken()];alter_midround_life_limit=1'>[CONFIG_GET(number/midround_antag_life_check) * 100]% of crew alive</a><BR>"
dat += "If limits past: <a href='?_src_=holder;[HrefToken()];toggle_noncontinuous_behavior=1'>[SSticker.mode.round_ends_with_antag_death ? "End The Round" : "Continue As Extended"]</a><BR>"
dat += "<a href='?_src_=holder;[HrefToken()];end_round=[REF(usr)]'>End Round Now</a><br>"
dat += "<a href='?_src_=holder;[HrefToken()];delay_round_end=1'>[SSticker.delay_end ? "End Round Normally" : "Delay Round End"]</a>"
var/connected_players = GLOB.clients.len
var/lobby_players = 0
var/observers = 0
var/observers_connected = 0
var/living_players = 0
var/living_players_connected = 0
var/living_players_antagonist = 0
var/brains = 0
var/other_players = 0
var/living_skipped = 0
var/drones = 0
for(var/mob/M in GLOB.mob_list)
if(M.ckey)
if(isnewplayer(M))
lobby_players++
continue
else if(M.stat != DEAD && M.mind && !isbrain(M))
if(isdrone(M))
drones++
continue
if(is_centcom_level(M.z))
living_skipped++
continue
living_players++
if(M.mind.special_role)
living_players_antagonist++
if(M.client)
living_players_connected++
else if(M.stat == DEAD || isobserver(M))
observers++
if(M.client)
observers_connected++
else if(isbrain(M))
brains++
else
other_players++
dat += "<BR><b><font color='blue' size='3'>Players:|[connected_players - lobby_players] ingame|[connected_players] connected|[lobby_players] lobby|</font></b>"
dat += "<BR><b><font color='green'>Living Players:|[living_players_connected] active|[living_players - living_players_connected] disconnected|[living_players_antagonist] antagonists|</font></b>"
dat += "<BR><b><font color='#bf42f4'>SKIPPED \[On centcom Z-level\]: [living_skipped] living players|[drones] living drones|</font></b>"
dat += "<BR><b><font color='red'>Dead/Observing players:|[observers_connected] active|[observers - observers_connected] disconnected|[brains] brains|</font></b>"
if(other_players)
dat += "<BR><span class='userdanger'>[other_players] players in invalid state or the statistics code is bugged!</span>"
dat += "<br><br>"
dat += build_antag_listing()
dat += "</body></html>"
usr << browse(dat.Join(), "window=roundstatus;size=500x500")
+41
View File
@@ -0,0 +1,41 @@
/datum/admins/proc/create_mob(mob/user)
var/static/create_mob_html
if (!create_mob_html)
var/mobjs = null
mobjs = jointext(typesof(/mob), ";")
create_mob_html = file2text('html/create_object.html')
create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob")
create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"")
user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475")
/proc/randomize_human(mob/living/carbon/human/H)
H.gender = pick(MALE, FEMALE)
H.real_name = random_unique_name(H.gender)
H.name = H.real_name
H.underwear = random_underwear(H.gender)
H.undie_color = random_short_color()
H.undershirt = random_undershirt(H.gender)
H.shirt_color = random_short_color()
H.skin_tone = random_skin_tone()
H.hair_style = random_hair_style(H.gender)
H.facial_hair_style = random_facial_hair_style(H.gender)
H.hair_color = random_short_color()
H.facial_hair_color = H.hair_color
H.eye_color = random_eye_color()
H.dna.blood_type = random_blood_type()
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
H.dna.features["mcolor"] = random_short_color()
H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard)
H.dna.features["snout"] = pick(GLOB.snouts_list)
H.dna.features["horns"] = pick(GLOB.horns_list)
H.dna.features["frills"] = pick(GLOB.frills_list)
H.dna.features["spines"] = pick(GLOB.spines_list)
H.dna.features["body_markings"] = pick(GLOB.body_markings_list)
H.dna.features["moth_wings"] = pick(GLOB.moth_wings_list)
H.update_body()
H.update_hair()
H.update_body_parts()
+32
View File
@@ -0,0 +1,32 @@
/datum/admins/proc/create_panel_helper(template)
var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]")
final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]")
return final_html
/datum/admins/proc/create_object(mob/user)
var/static/create_object_html = null
if (!create_object_html)
var/objectjs = null
objectjs = jointext(typesof(/obj), ";")
create_object_html = file2text('html/create_object.html')
create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"")
user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475")
/datum/admins/proc/quick_create_object(mob/user)
var/static/list/create_object_forms = list(
/obj, /obj/structure, /obj/machinery, /obj/effect,
/obj/item, /obj/item/clothing, /obj/item/stack, /obj/item,
/obj/item/reagent_containers, /obj/item/gun)
var/path = input("Select the path of the object you wish to create.", "Path", /obj) in create_object_forms
var/html_form = create_object_forms[path]
if (!html_form)
var/objectjs = jointext(typesof(path), ";")
html_form = file2text('html/create_object.html')
html_form = replacetext(html_form, "Create Object", "Create [path]")
html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"")
create_object_forms[path] = html_form
user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475")
+150
View File
@@ -0,0 +1,150 @@
/client/proc/create_poll()
set name = "Create Poll"
set category = "Special Verbs"
if(!check_rights(R_POLL))
return
if(!SSdbcore.Connect())
to_chat(src, "<span class='danger'>Failed to establish database connection.</span>")
return
var/polltype = input("Choose poll type.","Poll Type") as null|anything in list("Single Option","Text Reply","Rating","Multiple Choice", "Instant Runoff Voting")
var/choice_amount = 0
switch(polltype)
if("Single Option")
polltype = POLLTYPE_OPTION
if("Text Reply")
polltype = POLLTYPE_TEXT
if("Rating")
polltype = POLLTYPE_RATING
if("Multiple Choice")
polltype = POLLTYPE_MULTI
choice_amount = input("How many choices should be allowed?","Select choice amount") as num|null
switch(choice_amount)
if(0)
to_chat(src, "Multiple choice poll must have at least one choice allowed.")
return
if(1)
polltype = POLLTYPE_OPTION
if(null)
return
if ("Instant Runoff Voting")
polltype = POLLTYPE_IRV
else
return 0
var/starttime = SQLtime()
var/endtime = input("Set end time for poll as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than starting time for obvious reasons.", "Set end time", SQLtime()) as text
if(!endtime)
return
endtime = sanitizeSQL(endtime)
var/datum/DBQuery/query_validate_time = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[endtime]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[endtime]','%Y-%c-%d %T'), 0)")
if(!query_validate_time.warn_execute() || QDELETED(usr) || !src)
qdel(query_validate_time)
return
if(query_validate_time.NextRow())
var/checktime = text2num(query_validate_time.item[1])
if(!checktime)
to_chat(src, "Datetime entered is improperly formatted or not later than current server time.")
qdel(query_validate_time)
return
endtime = query_validate_time.item[1]
qdel(query_validate_time)
var/adminonly
switch(alert("Admin only poll?",,"Yes","No","Cancel"))
if("Yes")
adminonly = 1
if("No")
adminonly = 0
else
return
var/dontshow
switch(alert("Hide poll results from tracking until completed?",,"Yes","No","Cancel"))
if("Yes")
dontshow = 1
if("No")
dontshow = 0
else
return
var/sql_ckey = sanitizeSQL(ckey)
var/question = input("Write your question","Question") as message|null
if(!question)
return
question = sanitizeSQL(question)
var/list/sql_option_list = list()
if(polltype != POLLTYPE_TEXT)
var/add_option = 1
while(add_option)
var/option = input("Write your option","Option") as message|null
if(!option)
return
option = sanitizeSQL(option)
var/default_percentage_calc = 0
if(polltype != POLLTYPE_IRV)
switch(alert("Should this option be included by default when poll result percentages are generated?",,"Yes","No","Cancel"))
if("Yes")
default_percentage_calc = 1
if("No")
default_percentage_calc = 0
else
return
var/minval = 0
var/maxval = 0
var/descmin = ""
var/descmid = ""
var/descmax = ""
if(polltype == POLLTYPE_RATING)
minval = input("Set minimum rating value.","Minimum rating") as num|null
if(minval)
minval = sanitizeSQL(minval)
else if(minval == null)
return
maxval = input("Set maximum rating value.","Maximum rating") as num|null
if(maxval)
maxval = sanitizeSQL(maxval)
if(minval >= maxval)
to_chat(src, "Maximum rating value can't be less than or equal to minimum rating value")
continue
else if(maxval == null)
return
descmin = input("Optional: Set description for minimum rating","Minimum rating description") as message|null
if(descmin)
descmin = sanitizeSQL(descmin)
else if(descmin == null)
return
descmid = input("Optional: Set description for median rating","Median rating description") as message|null
if(descmid)
descmid = sanitizeSQL(descmid)
else if(descmid == null)
return
descmax = input("Optional: Set description for maximum rating","Maximum rating description") as message|null
if(descmax)
descmax = sanitizeSQL(descmax)
else if(descmax == null)
return
sql_option_list += list(list("text" = "'[option]'", "minval" = "'[minval]'", "maxval" = "'[maxval]'", "descmin" = "'[descmin]'", "descmid" = "'[descmid]'", "descmax" = "'[descmax]'", "default_percentage_calc" = "'[default_percentage_calc]'"))
switch(alert(" ",,"Add option","Finish", "Cancel"))
if("Add option")
add_option = 1
if("Finish")
add_option = 0
else
return 0
var/m1 = "[key_name(usr)] has created a new server poll. Poll type: [polltype] - Admin Only: [adminonly ? "Yes" : "No"] - Question: [question]"
var/m2 = "[key_name_admin(usr)] has created a new server poll. Poll type: [polltype] - Admin Only: [adminonly ? "Yes" : "No"]<br>Question: [question]"
var/datum/DBQuery/query_polladd_question = SSdbcore.NewQuery("INSERT INTO [format_table_name("poll_question")] (polltype, starttime, endtime, question, adminonly, multiplechoiceoptions, createdby_ckey, createdby_ip, dontshow) VALUES ('[polltype]', '[starttime]', '[endtime]', '[question]', '[adminonly]', '[choice_amount]', '[sql_ckey]', INET_ATON('[address]'), '[dontshow]')")
if(!query_polladd_question.warn_execute())
qdel(query_polladd_question)
return
qdel(query_polladd_question)
if(polltype != POLLTYPE_TEXT)
var/pollid = 0
var/datum/DBQuery/query_get_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()")
if(!query_get_id.warn_execute())
qdel(query_get_id)
return
if(query_get_id.NextRow())
pollid = query_get_id.item[1]
qdel(query_get_id)
for(var/list/i in sql_option_list)
i |= list("pollid" = "'[pollid]'")
SSdbcore.MassInsert(format_table_name("poll_option"), sql_option_list, warn = 1)
log_admin(m1)
message_admins(m2)
+10
View File
@@ -0,0 +1,10 @@
/datum/admins/proc/create_turf(mob/user)
var/static/create_turf_html
if (!create_turf_html)
var/turfjs = null
turfjs = jointext(typesof(/turf), ";")
create_turf_html = file2text('html/create_object.html')
create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf")
create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"")
user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475")
+168
View File
@@ -0,0 +1,168 @@
/obj/effect/fun_balloon
name = "fun balloon"
desc = "This is going to be a laugh riot."
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "syndballoon"
anchored = TRUE
var/popped = FALSE
/obj/effect/fun_balloon/Initialize()
. = ..()
SSobj.processing |= src
/obj/effect/fun_balloon/Destroy()
SSobj.processing -= src
. = ..()
/obj/effect/fun_balloon/process()
if(!popped && check() && !QDELETED(src))
popped = TRUE
effect()
pop()
/obj/effect/fun_balloon/proc/check()
return FALSE
/obj/effect/fun_balloon/proc/effect()
return
/obj/effect/fun_balloon/proc/pop()
visible_message("[src] pops!")
playsound(get_turf(src), 'sound/items/party_horn.ogg', 50, 1, -1)
qdel(src)
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/effect/fun_balloon/attack_ghost(mob/user)
if(!user.client || !user.client.holder || popped)
return
var/confirmation = alert("Pop [src]?","Fun Balloon","Yes","No")
if(confirmation == "Yes" && !popped)
popped = TRUE
effect()
pop()
/obj/effect/fun_balloon/sentience
name = "sentience fun balloon"
desc = "When this pops, things are gonna get more aware around here."
var/effect_range = 3
var/group_name = "a bunch of giant spiders"
/obj/effect/fun_balloon/sentience/effect()
var/list/bodies = list()
for(var/mob/living/M in range(effect_range, get_turf(src)))
bodies += M
var/question = "Would you like to be [group_name]?"
var/list/candidates = pollCandidatesForMobs(question, ROLE_PAI, null, FALSE, 100, bodies)
while(LAZYLEN(candidates) && LAZYLEN(bodies))
var/mob/dead/observer/C = pick_n_take(candidates)
var/mob/living/body = pick_n_take(bodies)
to_chat(body, "Your mob has been taken over by a ghost!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(body)])")
body.ghostize(0)
body.key = C.key
new /obj/effect/temp_visual/gravpush(get_turf(body))
/obj/effect/fun_balloon/sentience/emergency_shuttle
name = "shuttle sentience fun balloon"
var/trigger_time = 60
/obj/effect/fun_balloon/sentience/emergency_shuttle/check()
. = FALSE
if(SSshuttle.emergency && (SSshuttle.emergency.timeLeft() <= trigger_time) && (SSshuttle.emergency.mode == SHUTTLE_CALL))
. = TRUE
/obj/effect/fun_balloon/scatter
name = "scatter fun balloon"
desc = "When this pops, you're not going to be around here anymore."
var/effect_range = 5
/obj/effect/fun_balloon/scatter/effect()
for(var/mob/living/M in range(effect_range, get_turf(src)))
var/turf/T = find_safe_turf()
new /obj/effect/temp_visual/gravpush(get_turf(M))
M.forceMove(T)
to_chat(M, "<span class='notice'>Pop!</span>")
/obj/effect/station_crash
name = "station crash"
desc = "With no survivors!"
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "syndballoon"
anchored = TRUE
/obj/effect/station_crash/New()
for(var/S in SSshuttle.stationary)
var/obj/docking_port/stationary/SM = S
if(SM.id == "emergency_home")
var/new_dir = turn(SM.dir, 180)
SM.forceMove(get_ranged_target_turf(SM, new_dir, rand(3,15)))
break
qdel(src)
//Arena
/obj/effect/forcefield/arena_shuttle
name = "portal"
timeleft = 0
var/list/warp_points
/obj/effect/forcefield/arena_shuttle/Initialize()
. = ..()
for(var/obj/effect/landmark/shuttle_arena_safe/exit in GLOB.landmarks_list)
warp_points += exit
/obj/effect/forcefield/arena_shuttle/Bumped(atom/movable/AM)
if(!isliving(AM))
return
var/mob/living/L = AM
if(L.pulling && istype(L.pulling, /obj/item/bodypart/head))
to_chat(L, "Your offering is accepted. You may pass.")
qdel(L.pulling)
var/turf/LA = get_turf(pick(warp_points))
L.forceMove(LA)
L.hallucination = 0
to_chat(L, "<span class='reallybig redtext'>The battle is won. Your bloodlust subsides.</span>")
for(var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw in L)
qdel(chainsaw)
else
to_chat(L, "You are not yet worthy of passing. Drag a severed head to the barrier to be allowed entry to the hall of champions.")
/obj/effect/landmark/shuttle_arena_safe
name = "hall of champions"
desc = "For the winners."
/obj/effect/landmark/shuttle_arena_entrance
name = "the arena"
desc = "A lava filled battlefield."
/obj/effect/forcefield/arena_shuttle_entrance
name = "portal"
timeleft = 0
var/list/warp_points = list()
/obj/effect/forcefield/arena_shuttle_entrance/Bumped(atom/movable/AM)
if(!isliving(AM))
return
if(!warp_points.len)
for(var/obj/effect/landmark/shuttle_arena_entrance/S in GLOB.landmarks_list)
warp_points |= S
var/obj/effect/landmark/LA = pick(warp_points)
var/mob/living/M = AM
M.forceMove(get_turf(LA))
to_chat(M, "<span class='reallybig redtext'>You're trapped in a deadly arena! To escape, you'll need to drag a severed head to the escape portals.</span>")
spawn()
var/obj/effect/mine/pickup/bloodbath/B = new (M)
B.mineEffect(M)
/area/shuttle_arena
name = "arena"
has_gravity = STANDARD_GRAVITY
requires_power = FALSE
+210
View File
@@ -0,0 +1,210 @@
GLOBAL_LIST_EMPTY(admin_datums)
GLOBAL_PROTECT(admin_datums)
GLOBAL_LIST_EMPTY(protected_admins)
GLOBAL_PROTECT(protected_admins)
GLOBAL_VAR_INIT(href_token, GenerateToken())
GLOBAL_PROTECT(href_token)
/datum/admins
var/datum/admin_rank/rank
var/target
var/name = "nobody's admin datum (no rank)" //Makes for better runtimes
var/client/owner = null
var/fakekey = null
var/datum/marked_datum
var/spamcooldown = 0
var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable
var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message
var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message
var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel
var/admin_signature
var/href_token
var/deadmined
/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
QDEL_IN(src, 0)
CRASH("Admin proc call creation of admin datum")
return
if(!ckey)
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a ckey")
return
if(!istype(R))
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a rank")
return
target = ckey
name = "[ckey]'s admin datum ([R])"
rank = R
admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]"
href_token = GenerateToken()
if(R.rights & R_DEBUG) //grant profile access
world.SetConfig("APP/admin", ckey, "role=admin")
//only admins with +ADMIN start admined
if(protected)
GLOB.protected_admins[target] = src
if (force_active || (R.rights & R_AUTOLOGIN))
activate()
else
deactivate()
/datum/admins/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admins/proc/activate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins -= target
GLOB.admin_datums[target] = src
deadmined = FALSE
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
/datum/admins/proc/deactivate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins[target] = src
GLOB.admin_datums -= target
deadmined = TRUE
var/client/C
if ((C = owner) || (C = GLOB.directory[target]))
disassociate()
C.verbs += /client/proc/readmin
/datum/admins/proc/associate(client/C)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(istype(C))
if(C.ckey != target)
var/msg = " has attempted to associate with [target]'s admin datum"
message_admins("[key_name_admin(C)][msg]")
log_admin("[key_name(C)][msg]")
return
if (deadmined)
activate()
owner = C
owner.holder = src
owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system
owner.verbs -= /client/proc/readmin
GLOB.admins |= C
/datum/admins/proc/disassociate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(owner)
GLOB.admins -= owner
owner.remove_admin_verbs()
owner.holder = null
owner = null
/datum/admins/proc/check_for_rights(rights_required)
if(rights_required && !(rights_required & rank.rights))
return 0
return 1
/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other)
if(!other)
return 1 //they have no rights
if(rank.rights == R_EVERYTHING)
return 1 //we have all the rights
if(src == other)
return 1 //you always have more rights than yourself
if(rank.rights != other.rank.rights)
if( (rank.rights & other.rank.rights) == other.rank.rights )
return 1 //we have all the rights they have and more
return 0
/datum/admins/vv_edit_var(var_name, var_value)
return FALSE //nice try trialmin
/*
checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
if rights_required == 0, then it simply checks if they are an admin.
if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed
generally it would be used like so:
/proc/admin_proc()
if(!check_rights(R_ADMIN))
return
to_chat(world, "you have enough rights!")
NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call
you will have to do something like if(client.rights & R_ADMIN) yourself.
*/
/proc/check_rights(rights_required, show_msg=1)
if(usr && usr.client)
if (check_rights_for(usr.client, rights_required))
return 1
else
if(show_msg)
to_chat(usr, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>")
return 0
//probably a bit iffy - will hopefully figure out a better solution
/proc/check_if_greater_rights_than(client/other)
if(usr && usr.client)
if(usr.client.holder)
if(!other || !other.holder)
return 1
return usr.client.holder.check_if_greater_rights_than_holder(other.holder)
return 0
//This proc checks whether subject has at least ONE of the rights specified in rights_required.
/proc/check_rights_for(client/subject, rights_required)
if(subject && subject.holder)
return subject.holder.check_for_rights(rights_required)
return 0
/proc/GenerateToken()
. = ""
for(var/I in 1 to 32)
. += "[rand(10)]"
/proc/RawHrefToken(forceGlobal = FALSE)
var/tok = GLOB.href_token
if(!forceGlobal && usr)
var/client/C = usr.client
if(!C)
CRASH("No client for HrefToken()!")
var/datum/admins/holder = C.holder
if(holder)
tok = holder.href_token
return tok
/proc/HrefToken(forceGlobal = FALSE)
return "admin_token=[RawHrefToken(forceGlobal)]"
/proc/HrefTokenFormField(forceGlobal = FALSE)
return "<input type='hidden' name='admin_token' value='[RawHrefToken(forceGlobal)]'>"
+138
View File
@@ -0,0 +1,138 @@
/datum/ipintel
var/ip
var/intel = 0
var/cache = FALSE
var/cacheminutesago = 0
var/cachedate = ""
var/cacherealtime = 0
/datum/ipintel/New()
cachedate = SQLtime()
cacherealtime = world.realtime
/datum/ipintel/proc/is_valid()
. = FALSE
if (intel < 0)
return
if (intel <= CONFIG_GET(number/ipintel_rating_bad))
if (world.realtime < cacherealtime + (CONFIG_GET(number/ipintel_save_good) * 60 * 60 * 10))
return TRUE
else
if (world.realtime < cacherealtime + (CONFIG_GET(number/ipintel_save_bad) * 60 * 60 * 10))
return TRUE
/proc/get_ip_intel(ip, bypasscache = FALSE, updatecache = TRUE)
var/datum/ipintel/res = new()
res.ip = ip
. = res
if (!ip || !CONFIG_GET(string/ipintel_email) || !SSipintel.enabled)
return
if (!bypasscache)
var/datum/ipintel/cachedintel = SSipintel.cache[ip]
if (cachedintel && cachedintel.is_valid())
cachedintel.cache = TRUE
return cachedintel
if(SSdbcore.Connect())
var/rating_bad = CONFIG_GET(number/ipintel_rating_bad)
var/datum/DBQuery/query_get_ip_intel = SSdbcore.NewQuery({"
SELECT date, intel, TIMESTAMPDIFF(MINUTE,date,NOW())
FROM [format_table_name("ipintel")]
WHERE
ip = INET_ATON('[ip]')
AND ((
intel < [rating_bad]
AND
date + INTERVAL [CONFIG_GET(number/ipintel_save_good)] HOUR > NOW()
) OR (
intel >= [rating_bad]
AND
date + INTERVAL [CONFIG_GET(number/ipintel_save_bad)] HOUR > NOW()
))
"})
if(!query_get_ip_intel.Execute())
qdel(query_get_ip_intel)
return
if (query_get_ip_intel.NextRow())
res.cache = TRUE
res.cachedate = query_get_ip_intel.item[1]
res.intel = text2num(query_get_ip_intel.item[2])
res.cacheminutesago = text2num(query_get_ip_intel.item[3])
res.cacherealtime = world.realtime - (text2num(query_get_ip_intel.item[3])*10*60)
SSipintel.cache[ip] = res
qdel(query_get_ip_intel)
return
qdel(query_get_ip_intel)
res.intel = ip_intel_query(ip)
if (updatecache && res.intel >= 0)
SSipintel.cache[ip] = res
if(SSdbcore.Connect())
var/datum/DBQuery/query_add_ip_intel = SSdbcore.NewQuery("INSERT INTO [format_table_name("ipintel")] (ip, intel) VALUES (INET_ATON('[ip]'), [res.intel]) ON DUPLICATE KEY UPDATE intel = VALUES(intel), date = NOW()")
query_add_ip_intel.Execute()
qdel(query_add_ip_intel)
/proc/ip_intel_query(ip, var/retryed=0)
. = -1 //default
if (!ip)
return
if (SSipintel.throttle > world.timeofday)
return
if (!SSipintel.enabled)
return
var/list/http[] = world.Export("http://[CONFIG_GET(string/ipintel_domain)]/check.php?ip=[ip]&contact=[CONFIG_GET(string/ipintel_email)]&format=json&flags=f")
if (http)
var/status = text2num(http["STATUS"])
if (status == 200)
var/response = json_decode(file2text(http["CONTENT"]))
if (response)
if (response["status"] == "success")
var/intelnum = text2num(response["result"])
if (isnum(intelnum))
return text2num(response["result"])
else
ipintel_handle_error("Bad intel from server: [response["result"]].", ip, retryed)
if (!retryed)
sleep(25)
return .(ip, 1)
else
ipintel_handle_error("Bad response from server: [response["status"]].", ip, retryed)
if (!retryed)
sleep(25)
return .(ip, 1)
else if (status == 429)
ipintel_handle_error("Error #429: We have exceeded the rate limit.", ip, 1)
return
else
ipintel_handle_error("Unknown status code: [status].", ip, retryed)
if (!retryed)
sleep(25)
return .(ip, 1)
else
ipintel_handle_error("Unable to connect to API.", ip, retryed)
if (!retryed)
sleep(25)
return .(ip, 1)
/proc/ipintel_handle_error(error, ip, retryed)
if (retryed)
SSipintel.errors++
error += " Could not check [ip]. Disabling IPINTEL for [SSipintel.errors] minute[( SSipintel.errors == 1 ? "" : "s" )]"
SSipintel.throttle = world.timeofday + (10 * 120 * SSipintel.errors)
else
error += " Attempting retry on [ip]."
log_ipintel(error)
/proc/log_ipintel(text)
log_game("IPINTEL: [text]")
debug_admins("IPINTEL: [text]")
+463
View File
@@ -0,0 +1,463 @@
/client/proc/edit_admin_permissions()
set category = "Admin"
set name = "Permissions Panel"
set desc = "Edit admin permissions"
if(!check_rights(R_PERMISSIONS))
return
usr.client.holder.edit_admin_permissions()
/datum/admins/proc/edit_admin_permissions(action, target, operation, page)
if(!check_rights(R_PERMISSIONS))
return
var/list/output = list("<link rel='stylesheet' type='text/css' href='panels.css'><a href='?_src_=holder;[HrefToken()];editrightsbrowser=1'>\[Permissions\]</a>")
if(action)
output += " | <a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightspage=0'>\[Log\]</a> | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1'>\[Management\]</a><hr style='background:#000000; border:0; height:3px'>"
else
output += "<br><a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightspage=0'>\[Log\]</a><br><a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1'>\[Management\]</a>"
if(action == 1)
var/list/searchlist = list(" WHERE ")
if(target)
searchlist += "ckey = '[sanitizeSQL(target)]'"
if(operation)
if(target)
searchlist += " AND "
searchlist += "operation = '[sanitizeSQL(operation)]'"
var/search
if(searchlist.len > 1)
search = searchlist.Join("")
var/logcount = 0
var/logssperpage = 20
var/pagecount = 0
page = text2num(page)
var/datum/DBQuery/query_count_admin_logs = SSdbcore.NewQuery("SELECT COUNT(id) FROM [format_table_name("admin_log")][search]")
if(!query_count_admin_logs.warn_execute())
qdel(query_count_admin_logs)
return
if(query_count_admin_logs.NextRow())
logcount = text2num(query_count_admin_logs.item[1])
qdel(query_count_admin_logs)
if(logcount > logssperpage)
output += "<br><b>Page: </b>"
while(logcount > 0)
output += "|<a href='?_src_=holder;[HrefToken()];editrightsbrowserlog=1;editrightstarget=[target];editrightsoperation=[operation];editrightspage=[pagecount]'>[pagecount == page ? "<b>\[[pagecount]\]</b>" : "\[[pagecount]\]"]</a>"
logcount -= logssperpage
pagecount++
output += "|"
var/limit = " LIMIT [logssperpage * page], [logssperpage]"
var/datum/DBQuery/query_search_admin_logs = SSdbcore.NewQuery("SELECT datetime, round_id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), operation, IF(ckey IS NULL, target, byond_key), log FROM [format_table_name("admin_log")] LEFT JOIN [format_table_name("player")] ON target = ckey[search] ORDER BY datetime DESC[limit]")
if(!query_search_admin_logs.warn_execute())
qdel(query_search_admin_logs)
return
while(query_search_admin_logs.NextRow())
var/datetime = query_search_admin_logs.item[1]
var/round_id = query_search_admin_logs.item[2]
var/admin_key = query_search_admin_logs.item[3]
operation = query_search_admin_logs.item[4]
target = query_search_admin_logs.item[5]
var/log = query_search_admin_logs.item[6]
output += "<p style='margin:0px'><b>[datetime] | Round ID [round_id] | Admin [admin_key] | Operation [operation] on [target]</b><br>[log]</p><hr style='background:#000000; border:0; height:3px'>"
qdel(query_search_admin_logs)
if(action == 2)
output += "<h3>Admin ckeys with invalid ranks</h3>"
var/datum/DBQuery/query_check_admin_errors = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("admin")].ckey), ckey), [format_table_name("admin")].rank FROM [format_table_name("admin")] LEFT JOIN [format_table_name("admin_ranks")] ON [format_table_name("admin_ranks")].rank = [format_table_name("admin")].rank WHERE [format_table_name("admin_ranks")].rank IS NULL")
if(!query_check_admin_errors.warn_execute())
qdel(query_check_admin_errors)
return
while(query_check_admin_errors.NextRow())
var/admin_key = query_check_admin_errors.item[1]
var/admin_rank = query_check_admin_errors.item[2]
output += "[admin_key] has non-existent rank [admin_rank] | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1;editrightschange=[admin_key]'>\[Change Rank\]</a> | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1;editrightsremove=[admin_key]'>\[Remove\]</a>"
output += "<hr style='background:#000000; border:0; height:1px'>"
qdel(query_check_admin_errors)
output += "<h3>Unused ranks</h3>"
var/datum/DBQuery/query_check_unused_rank = SSdbcore.NewQuery("SELECT [format_table_name("admin_ranks")].rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] LEFT JOIN [format_table_name("admin")] ON [format_table_name("admin")].rank = [format_table_name("admin_ranks")].rank WHERE [format_table_name("admin")].rank IS NULL")
if(!query_check_unused_rank.warn_execute())
qdel(query_check_unused_rank)
return
while(query_check_unused_rank.NextRow())
var/admin_rank = query_check_unused_rank.item[1]
output += {"Rank [admin_rank] is not held by any admin | <a href='?_src_=holder;[HrefToken()];editrightsbrowsermanage=1;editrightsremoverank=[admin_rank]'>\[Remove\]</a>
<br>Permissions: [rights2text(text2num(query_check_unused_rank.item[2])," ")]
<br>Denied: [rights2text(text2num(query_check_unused_rank.item[3])," ", "-")]
<br>Allowed to edit: [rights2text(text2num(query_check_unused_rank.item[4])," ", "*")]
<hr style='background:#000000; border:0; height:1px'>"}
qdel(query_check_unused_rank)
else if(!action)
output += {"<head>
<title>Permissions Panel</title>
<script type='text/javascript' src='search.js'></script>
</head>
<body onload='selectTextField();updateSearch();'>
<div id='main'><table id='searchable' cellspacing='0'>
<tr class='title'>
<th style='width:150px;'>CKEY <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=add'>\[+\]</a></th>
<th style='width:125px;'>RANK</th>
<th style='width:40%;'>PERMISSIONS</th>
<th style='width:20%;'>DENIED</th>
<th style='width:40%;'>ALLOWED TO EDIT</th>
</tr>
"}
for(var/adm_ckey in GLOB.admin_datums+GLOB.deadmins)
var/datum/admins/D = GLOB.admin_datums[adm_ckey]
if(!D)
D = GLOB.deadmins[adm_ckey]
if (!D)
continue
var/deadminlink = ""
if(D.owner)
adm_ckey = D.owner.key
if (D.deadmined)
deadminlink = " <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=activate;key=[adm_ckey]'>\[RA\]</a>"
else
deadminlink = " <a class='small' href='?src=[REF(src)];[HrefToken()];editrights=deactivate;key=[adm_ckey]'>\[DA\]</a>"
output += "<tr>"
output += "<td style='text-align:center;'>[adm_ckey]<br>[deadminlink]<a class='small' href='?src=[REF(src)];[HrefToken()];editrights=remove;key=[adm_ckey]'>\[-\]</a><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=sync;key=[adm_ckey]'>\[SYNC TGDB\]</a></td>"
output += "<td><a href='?src=[REF(src)];[HrefToken()];editrights=rank;key=[adm_ckey]'>[D.rank.name]</a></td>"
output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rank.include_rights," ")]</a></td>"
output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rank.exclude_rights," ", "-")]</a></td>"
output += "<td><a class='small' href='?src=[REF(src)];[HrefToken()];editrights=permissions;key=[adm_ckey]'>[rights2text(D.rank.can_edit_rights," ", "*")]</a></td>"
output += "</tr>"
output += "</table></div><div id='top'><b>Search:</b> <input type='text' id='filter' value='' style='width:70%;' onkeyup='updateSearch();'></div></body>"
if(QDELETED(usr))
return
usr << browse("<!DOCTYPE html><html>[jointext(output, "")]</html>","window=editrights;size=1000x650")
/datum/admins/proc/edit_rights_topic(list/href_list)
if(!check_rights(R_PERMISSIONS))
message_admins("[key_name_admin(usr)] attempted to edit admin permissions without sufficient rights.")
log_admin("[key_name(usr)] attempted to edit admin permissions without sufficient rights.")
return
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Edit blocked: Advanced ProcCall detected.</span>")
return
var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/permissions)
permissions_assets.send(src)
var/admin_key = href_list["key"]
var/admin_ckey = ckey(admin_key)
var/datum/admins/D = GLOB.admin_datums[admin_ckey]
var/use_db
var/task = href_list["editrights"]
var/skip
var/legacy_only
if(task == "activate" || task == "deactivate" || task == "sync")
skip = TRUE
if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_admins) && task == "rank")
if(admin_ckey in GLOB.protected_admins)
to_chat(usr, "<span class='admin prefix'>Editing the rank of this admin is blocked by server configuration.</span>")
return
if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && task == "permissions")
if(D.rank in GLOB.protected_ranks)
to_chat(usr, "<span class='admin prefix'>Editing the flags of this rank is blocked by server configuration.</span>")
return
if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "add" || task == "rank" || task == "permissions"))
to_chat(usr, "<span class='admin prefix'>Database rank loading is disabled, only temporary changes can be made to a rank's permissions and permanently creating a new rank is blocked.</span>")
legacy_only = TRUE
if(check_rights(R_DBRANKS, FALSE))
if(!skip)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Unable to connect to database, changes are temporary only.</span>")
use_db = FALSE
else
use_db = alert("Permanent changes are saved to the database for future rounds, temporary changes will affect only the current round", "Permanent or Temporary?", "Permanent", "Temporary", "Cancel")
if(use_db == "Cancel")
return
if(use_db == "Permanent")
use_db = TRUE
admin_ckey = sanitizeSQL(admin_ckey)
else
use_db = FALSE
if(QDELETED(usr))
return
if(task != "add")
D = GLOB.admin_datums[admin_ckey]
if(!D)
D = GLOB.deadmins[admin_ckey]
if(!D)
return
if((task != "sync") && !check_if_greater_rights_than_holder(D))
message_admins("[key_name_admin(usr)] attempted to change the rank of [admin_key] without sufficient rights.")
log_admin("[key_name(usr)] attempted to change the rank of [admin_key] without sufficient rights.")
return
switch(task)
if("add")
admin_ckey = add_admin(admin_ckey, admin_key, use_db)
if(!admin_ckey)
return
change_admin_rank(admin_ckey, admin_key, use_db, null, legacy_only)
if("remove")
remove_admin(admin_ckey, admin_key, use_db, D)
if("rank")
change_admin_rank(admin_ckey, admin_key, use_db, D, legacy_only)
if("permissions")
change_admin_flags(admin_ckey, admin_key, use_db, D, legacy_only)
if("activate")
force_readmin(admin_key, D)
if("deactivate")
force_deadmin(admin_key, D)
if("sync")
sync_lastadminrank(admin_ckey, admin_key, D)
edit_admin_permissions()
/datum/admins/proc/add_admin(admin_ckey, admin_key, use_db)
if(admin_ckey)
. = admin_ckey
else
admin_key = input("New admin's key","Admin key") as text|null
. = ckey(admin_key)
if(!.)
return FALSE
if(!admin_ckey && (. in GLOB.admin_datums+GLOB.deadmins))
to_chat(usr, "<span class='danger'>[admin_key] is already an admin.</span>")
return FALSE
if(use_db)
. = sanitizeSQL(.)
//if an admin exists without a datum they won't be caught by the above
var/datum/DBQuery/query_admin_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin")] WHERE ckey = '[.]'")
if(!query_admin_in_db.warn_execute())
qdel(query_admin_in_db)
return FALSE
if(query_admin_in_db.NextRow())
qdel(query_admin_in_db)
to_chat(usr, "<span class='danger'>[admin_key] already listed in admin database. Check the Management tab if they don't appear in the list of admins.</span>")
return FALSE
qdel(query_admin_in_db)
var/datum/DBQuery/query_add_admin = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin")] (ckey, rank) VALUES ('[.]', 'NEW ADMIN')")
if(!query_add_admin.warn_execute())
qdel(query_add_admin)
return FALSE
qdel(query_add_admin)
var/datum/DBQuery/query_add_admin_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'add admin', '[.]', 'New admin added: [.]')")
if(!query_add_admin_log.warn_execute())
qdel(query_add_admin_log)
return FALSE
qdel(query_add_admin_log)
/datum/admins/proc/remove_admin(admin_ckey, admin_key, use_db, datum/admins/D)
if(alert("Are you sure you want to remove [admin_ckey]?","Confirm Removal","Do it","Cancel") == "Do it")
GLOB.admin_datums -= admin_ckey
GLOB.deadmins -= admin_ckey
if(D)
D.disassociate()
var/m1 = "[key_name_admin(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]"
var/m2 = "[key_name(usr)] removed [admin_key] from the admins list [use_db ? "permanently" : "temporarily"]"
if(use_db)
var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("DELETE FROM [format_table_name("admin")] WHERE ckey = '[admin_ckey]'")
if(!query_add_rank.warn_execute())
qdel(query_add_rank)
return
qdel(query_add_rank)
var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'remove admin', '[admin_ckey]', 'Admin removed: [admin_ckey]')")
if(!query_add_rank_log.warn_execute())
qdel(query_add_rank_log)
return
qdel(query_add_rank_log)
sync_lastadminrank(admin_ckey, admin_key)
message_admins(m1)
log_admin(m2)
/datum/admins/proc/force_readmin(admin_key, datum/admins/D)
if(!D || !D.deadmined)
return
D.activate()
message_admins("[key_name_admin(usr)] forcefully readmined [admin_key]")
log_admin("[key_name(usr)] forcefully readmined [admin_key]")
/datum/admins/proc/force_deadmin(admin_key, datum/admins/D)
if(!D || D.deadmined)
return
message_admins("[key_name_admin(usr)] forcefully deadmined [admin_key]")
log_admin("[key_name(usr)] forcefully deadmined [admin_key]")
D.deactivate() //after logs so the deadmined admin can see the message.
/datum/admins/proc/change_admin_rank(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only)
var/datum/admin_rank/R
var/list/rank_names = list()
if(!use_db || (use_db && !legacy_only))
rank_names += "*New Rank*"
for(R in GLOB.admin_ranks)
if((R.rights & usr.client.holder.rank.can_edit_rights) == R.rights)
rank_names[R.name] = R
var/new_rank = input("Please select a rank", "New rank") as null|anything in rank_names
if(new_rank == "*New Rank*")
new_rank = ckeyEx(input("Please input a new rank", "New custom rank") as text|null)
if(!new_rank)
return
R = rank_names[new_rank]
if(!R) //rank with that name doesn't exist yet - make it
if(D)
R = new(new_rank, D.rank.rights) //duplicate our previous admin_rank but with a new name
else
R = new(new_rank) //blank new admin_rank
GLOB.admin_ranks += R
var/m1 = "[key_name_admin(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]"
var/m2 = "[key_name(usr)] edited the admin rank of [admin_key] to [new_rank] [use_db ? "permanently" : "temporarily"]"
if(use_db)
new_rank = sanitizeSQL(new_rank)
//if a player was tempminned before having a permanent change made to their rank they won't yet be in the db
var/old_rank
var/datum/DBQuery/query_admin_in_db = SSdbcore.NewQuery("SELECT rank FROM [format_table_name("admin")] WHERE ckey = '[admin_ckey]'")
if(!query_admin_in_db.warn_execute())
qdel(query_admin_in_db)
return
if(!query_admin_in_db.NextRow())
add_admin(admin_ckey, admin_key, TRUE)
old_rank = "NEW ADMIN"
else
old_rank = query_admin_in_db.item[1]
qdel(query_admin_in_db)
//similarly if a temp rank is created it won't be in the db if someone is permanently changed to it
var/datum/DBQuery/query_rank_in_db = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin_ranks")] WHERE rank = '[new_rank]'")
if(!query_rank_in_db.warn_execute())
qdel(query_rank_in_db)
return
if(!query_rank_in_db.NextRow())
QDEL_NULL(query_rank_in_db)
var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_ranks")] (rank, flags, exclude_flags, can_edit_flags) VALUES ('[new_rank]', '0', '0', '0')")
if(!query_add_rank.warn_execute())
qdel(query_add_rank)
return
qdel(query_add_rank)
var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'add rank', '[new_rank]', 'New rank added: [new_rank]')")
if(!query_add_rank_log.warn_execute())
qdel(query_add_rank_log)
return
qdel(query_add_rank_log)
qdel(query_rank_in_db)
var/datum/DBQuery/query_change_rank = SSdbcore.NewQuery("UPDATE [format_table_name("admin")] SET rank = '[new_rank]' WHERE ckey = '[admin_ckey]'")
if(!query_change_rank.warn_execute())
qdel(query_change_rank)
return
qdel(query_change_rank)
var/datum/DBQuery/query_change_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'change admin rank', '[admin_ckey]', 'Rank of [admin_ckey] changed from [old_rank] to [new_rank]')")
if(!query_change_rank_log.warn_execute())
qdel(query_change_rank_log)
return
qdel(query_change_rank_log)
if(D) //they were previously an admin
D.disassociate() //existing admin needs to be disassociated
D.rank = R //set the admin_rank as our rank
var/client/C = GLOB.directory[admin_ckey]
D.associate(C)
else
D = new(R, admin_ckey, TRUE) //new admin
message_admins(m1)
log_admin(m2)
/datum/admins/proc/change_admin_flags(admin_ckey, admin_key, use_db, datum/admins/D, legacy_only)
var/new_flags = input_bitfield(usr, "Include permission flags<br>[use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.include_rights, 350, 590, allowed_edit_list = usr.client.holder.rank.can_edit_rights)
if(isnull(new_flags))
return
var/new_exclude_flags = input_bitfield(usr, "Exclude permission flags<br>Flags enabled here will be removed from a rank.<br>Note these take precedence over included flags.<br>[use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.exclude_rights, 350, 670, "red", usr.client.holder.rank.can_edit_rights)
if(isnull(new_exclude_flags))
return
var/new_can_edit_flags = input_bitfield(usr, "Editable permission flags<br>These are the flags this rank is allowed to edit if they have access to the permissions panel.<br>They will be unable to modify admins to a rank that has a flag not included here.<br>[use_db ? "This will affect ALL admins with this rank." : "This will affect only the current admin [admin_key]"]", "admin_flags", D.rank.can_edit_rights, 350, 710, allowed_edit_list = usr.client.holder.rank.can_edit_rights)
if(isnull(new_can_edit_flags))
return
var/m1 = "[key_name_admin(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]"
var/m2 = "[key_name(usr)] edited the permissions of [use_db ? " rank [D.rank.name] permanently" : "[admin_key] temporarily"]"
if(use_db || legacy_only)
var/old_flags
var/old_exclude_flags
var/old_can_edit_flags
var/datum/DBQuery/query_get_rank_flags = SSdbcore.NewQuery("SELECT flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")] WHERE rank = '[D.rank.name]'")
if(!query_get_rank_flags.warn_execute())
qdel(query_get_rank_flags)
return
if(query_get_rank_flags.NextRow())
old_flags = text2num(query_get_rank_flags.item[1])
old_exclude_flags = text2num(query_get_rank_flags.item[2])
old_can_edit_flags = text2num(query_get_rank_flags.item[3])
qdel(query_get_rank_flags)
var/datum/DBQuery/query_change_rank_flags = SSdbcore.NewQuery("UPDATE [format_table_name("admin_ranks")] SET flags = '[new_flags]', exclude_flags = '[new_exclude_flags]', can_edit_flags = '[new_can_edit_flags]' WHERE rank = '[D.rank.name]'")
if(!query_change_rank_flags.warn_execute())
qdel(query_change_rank_flags)
return
qdel(query_change_rank_flags)
var/datum/DBQuery/query_change_rank_flags_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'change rank flags', '[D.rank.name]', 'Permissions of [D.rank.name] changed from[rights2text(old_flags," ")][rights2text(old_exclude_flags," ", "-")][rights2text(old_can_edit_flags," ", "*")] to[rights2text(new_flags," ")][rights2text(new_exclude_flags," ", "-")][rights2text(new_can_edit_flags," ", "*")]')")
if(!query_change_rank_flags_log.warn_execute())
qdel(query_change_rank_flags_log)
return
qdel(query_change_rank_flags_log)
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name != D.rank.name)
continue
R.rights = new_flags &= ~new_exclude_flags
R.exclude_rights = new_exclude_flags
R.include_rights = new_flags
R.can_edit_rights = new_can_edit_flags
for(var/i in GLOB.admin_datums+GLOB.deadmins)
var/datum/admins/A = GLOB.admin_datums[i]
if(!A)
A = GLOB.deadmins[i]
if (!A)
continue
if(A.rank.name != D.rank.name)
continue
var/client/C = GLOB.directory[A.target]
A.disassociate()
A.associate(C)
else
D.disassociate()
if(!findtext(D.rank.name, "([admin_ckey])")) //not a modified subrank, need to duplicate the admin_rank datum to prevent modifying others too
D.rank = new("[D.rank.name]([admin_ckey])", new_flags, new_exclude_flags, new_can_edit_flags) //duplicate our previous admin_rank but with a new name
//we don't add this clone to the admin_ranks list, as it is unique to that ckey
else
D.rank.rights = new_flags &= ~new_exclude_flags
D.rank.include_rights = new_flags
D.rank.exclude_rights = new_exclude_flags
D.rank.can_edit_rights = new_can_edit_flags
var/client/C = GLOB.directory[admin_ckey] //find the client with the specified ckey (if they are logged in)
D.associate(C) //link up with the client and add verbs
message_admins(m1)
log_admin(m2)
/datum/admins/proc/remove_rank(admin_rank)
if(!admin_rank)
return
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == admin_rank && (!(R.rights & usr.client.holder.rank.can_edit_rights) == R.rights))
to_chat(usr, "<span class='admin prefix'>You don't have edit rights to all the rights this rank has, rank deletion not permitted.</span>")
return
if(!CONFIG_GET(flag/admin_legacy_system) && CONFIG_GET(flag/protect_legacy_ranks) && (admin_rank in GLOB.protected_ranks))
to_chat(usr, "<span class='admin prefix'>Deletion of protected ranks is not permitted, it must be removed from admin_ranks.txt.</span>")
return
if(CONFIG_GET(flag/load_legacy_ranks_only))
to_chat(usr, "<span class='admin prefix'>Rank deletion not permitted while database rank loading is disabled.</span>")
return
admin_rank = sanitizeSQL(admin_rank)
var/datum/DBQuery/query_admins_with_rank = SSdbcore.NewQuery("SELECT 1 FROM [format_table_name("admin")] WHERE rank = '[admin_rank]'")
if(!query_admins_with_rank.warn_execute())
qdel(query_admins_with_rank)
return
if(query_admins_with_rank.NextRow())
qdel(query_admins_with_rank)
to_chat(usr, "<span class='danger'>Error: Rank deletion attempted while rank still used; Tell a coder, this shouldn't happen.</span>")
return
qdel(query_admins_with_rank)
if(alert("Are you sure you want to remove [admin_rank]?","Confirm Removal","Do it","Cancel") == "Do it")
var/m1 = "[key_name_admin(usr)] removed rank [admin_rank] permanently"
var/m2 = "[key_name(usr)] removed rank [admin_rank] permanently"
var/datum/DBQuery/query_add_rank = SSdbcore.NewQuery("DELETE FROM [format_table_name("admin_ranks")] WHERE rank = '[admin_rank]'")
if(!query_add_rank.warn_execute())
qdel(query_add_rank)
return
qdel(query_add_rank)
var/datum/DBQuery/query_add_rank_log = SSdbcore.NewQuery("INSERT INTO [format_table_name("admin_log")] (datetime, round_id, adminckey, adminip, operation, target, log) VALUES ('[SQLtime()]', '[GLOB.round_id]', '[sanitizeSQL(usr.ckey)]', INET_ATON('[sanitizeSQL(usr.client.address)]'), 'remove rank', '[admin_rank]', 'Rank removed: [admin_rank]')")
if(!query_add_rank_log.warn_execute())
qdel(query_add_rank_log)
return
qdel(query_add_rank_log)
message_admins(m1)
log_admin(m2)
/datum/admins/proc/sync_lastadminrank(admin_ckey, admin_key, datum/admins/D)
var/sqlrank = "Player"
if (D)
sqlrank = sanitizeSQL(D.rank.name)
admin_ckey = sanitizeSQL(admin_ckey)
var/datum/DBQuery/query_sync_lastadminrank = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastadminrank = '[sqlrank]' WHERE ckey = '[admin_ckey]'")
if(!query_sync_lastadminrank.warn_execute())
qdel(query_sync_lastadminrank)
return
qdel(query_sync_lastadminrank)
to_chat(usr, "<span class='admin'>Sync of [admin_key] successful.</span>")
+313
View File
@@ -0,0 +1,313 @@
/datum/admins/proc/player_panel_new()//The new one
if(!check_rights())
return
log_admin("[key_name(usr)] checked the player panel.")
var/dat = "<html><head><title>Player Panel</title></head>"
//javascript, the part that does most of the work~
dat += {"
<head>
<script type='text/javascript'>
var locked_tabs = new Array();
function updateSearch(){
var filter_text = document.getElementById('filter');
var filter = filter_text.value.toLowerCase();
if(complete_list != null && complete_list != ""){
var mtbl = document.getElementById("maintable_data_archive");
mtbl.innerHTML = complete_list;
}
if(filter.value == ""){
return;
}else{
var maintable_data = document.getElementById('maintable_data');
var ltr = maintable_data.getElementsByTagName("tr");
for ( var i = 0; i < ltr.length; ++i )
{
try{
var tr = ltr\[i\];
if(tr.getAttribute("id").indexOf("data") != 0){
continue;
}
var ltd = tr.getElementsByTagName("td");
var td = ltd\[0\];
var lsearch = td.getElementsByTagName("b");
var search = lsearch\[0\];
//var inner_span = li.getElementsByTagName("span")\[1\] //Should only ever contain one element.
//document.write("<p>"+search.innerText+"<br>"+filter+"<br>"+search.innerText.indexOf(filter))
if ( search.innerText.toLowerCase().indexOf(filter) == -1 )
{
//document.write("a");
//ltr.removeChild(tr);
td.innerHTML = "";
i--;
}
}catch(err) { }
}
}
var count = 0;
var index = -1;
var debug = document.getElementById("debug");
locked_tabs = new Array();
}
function expand(id,job,name,real_name,image,key,ip,antagonist,ref){
clearAll();
var span = document.getElementById(id);
var ckey = key.toLowerCase().replace(/\[^a-z@0-9\]+/g,"");
body = "<table><tr><td>";
body += "</td><td align='center'>";
body += "<font size='2'><b>"+job+" "+name+"</b><br><b>Real name "+real_name+"</b><br><b>Played by "+key+" ("+ip+")</b></font>"
body += "</td><td align='center'>";
body += "<a href='?_src_=holder;[HrefToken()];adminplayeropts="+ref+"'>PP</a> - "
body += "<a href='?_src_=holder;[HrefToken()];showmessageckey="+ckey+"'>N</a> - "
body += "<a href='?_src_=vars;[HrefToken()];Vars="+ref+"'>VV</a> - "
body += "<a href='?_src_=holder;[HrefToken()];traitor="+ref+"'>TP</a> - "
if (job == "Cyborg")
body += "<a href='?_src_=holder;[HrefToken()];borgpanel="+ref+"'>BP</a> - "
body += "<a href='?priv_msg="+ckey+"'>PM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];subtlemessage="+ref+"'>SM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];adminplayerobservefollow="+ref+"'>FLW</a> - "
body += "<a href='?_src_=holder;[HrefToken()];individuallog="+ref+"'>LOGS</a><br>"
if(antagonist > 0)
body += "<font size='2'><a href='?_src_=holder;[HrefToken()];secrets=check_antagonist'><font color='red'><b>Antagonist</b></font></a></font>";
body += "</td></tr></table>";
span.innerHTML = body
}
function clearAll(){
var spans = document.getElementsByTagName('span');
for(var i = 0; i < spans.length; i++){
var span = spans\[i\];
var id = span.getAttribute("id");
if(!(id.indexOf("item")==0))
continue;
var pass = 1;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 0;
break;
}
}
if(pass != 1)
continue;
span.innerHTML = "";
}
}
function addToLocked(id,link_id,notice_span_id){
var link = document.getElementById(link_id);
var decision = link.getAttribute("name");
if(decision == "1"){
link.setAttribute("name","2");
}else{
link.setAttribute("name","1");
removeFromLocked(id,link_id,notice_span_id);
return;
}
var pass = 1;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 0;
break;
}
}
if(!pass)
return;
locked_tabs.push(id);
var notice_span = document.getElementById(notice_span_id);
notice_span.innerHTML = "<font color='red'>Locked</font> ";
//link.setAttribute("onClick","attempt('"+id+"','"+link_id+"','"+notice_span_id+"');");
//document.write("removeFromLocked('"+id+"','"+link_id+"','"+notice_span_id+"')");
//document.write("aa - "+link.getAttribute("onClick"));
}
function attempt(ab){
return ab;
}
function removeFromLocked(id,link_id,notice_span_id){
//document.write("a");
var index = 0;
var pass = 0;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 1;
index = j;
break;
}
}
if(!pass)
return;
locked_tabs\[index\] = "";
var notice_span = document.getElementById(notice_span_id);
notice_span.innerHTML = "";
//var link = document.getElementById(link_id);
//link.setAttribute("onClick","addToLocked('"+id+"','"+link_id+"','"+notice_span_id+"')");
}
function selectTextField(){
var filter_text = document.getElementById('filter');
filter_text.focus();
filter_text.select();
}
</script>
</head>
"}
//body tag start + onload and onkeypress (onkeyup) javascript event calls
dat += "<body onload='selectTextField(); updateSearch();' onkeyup='updateSearch();'>"
//title + search bar
dat += {"
<table width='560' align='center' cellspacing='0' cellpadding='5' id='maintable'>
<tr id='title_tr'>
<td align='center'>
<font size='5'><b>Player panel</b></font><br>
Hover over a line to see more information - <a href='?_src_=holder;[HrefToken()];check_antagonist=1'>Check antagonists</a> - Kick <a href='?_src_=holder;[HrefToken()];kick_all_from_lobby=1;afkonly=0'>everyone</a>/<a href='?_src_=holder;[HrefToken()];kick_all_from_lobby=1;afkonly=1'>AFKers</a> in lobby
<p>
</td>
</tr>
<tr id='search_tr'>
<td align='center'>
<b>Search:</b> <input type='text' id='filter' value='' style='width:300px;'>
</td>
</tr>
</table>
"}
//player table header
dat += {"
<span id='maintable_data_archive'>
<table width='560' align='center' cellspacing='0' cellpadding='5' id='maintable_data'>"}
var/list/mobs = sortmobs()
var/i = 1
for(var/mob/M in mobs)
if(M.ckey)
var/color = "#e6e6e6"
if(i%2 == 0)
color = "#f2f2f2"
var/is_antagonist = is_special_character(M)
var/M_job = ""
if(isliving(M))
if(iscarbon(M)) //Carbon stuff
if(ishuman(M))
M_job = M.job
else if(ismonkey(M))
M_job = "Monkey"
else if(isalien(M)) //aliens
if(islarva(M))
M_job = "Alien larva"
else
M_job = ROLE_ALIEN
else
M_job = "Carbon-based"
else if(issilicon(M)) //silicon
if(isAI(M))
M_job = "AI"
else if(ispAI(M))
M_job = ROLE_PAI
else if(iscyborg(M))
M_job = "Cyborg"
else
M_job = "Silicon-based"
else if(isanimal(M)) //simple animals
if(iscorgi(M))
M_job = "Corgi"
else if(isslime(M))
M_job = "slime"
else
M_job = "Animal"
else
M_job = "Living"
else if(isnewplayer(M))
M_job = "New player"
else if(isobserver(M))
var/mob/dead/observer/O = M
if(O.started_as_observer)//Did they get BTFO or are they just not trying?
M_job = "Observer"
else
M_job = "Ghost"
var/M_name = html_encode(M.name)
var/M_rname = html_encode(M.real_name)
var/M_key = html_encode(M.key)
//output for each mob
dat += {"
<tr id='data[i]' name='[i]' onClick="addToLocked('item[i]','data[i]','notice_span[i]')">
<td align='center' bgcolor='[color]'>
<span id='notice_span[i]'></span>
<a id='link[i]'
onmouseover='expand("item[i]","[M_job]","[M_name]","[M_rname]","--unused--","[M_key]","[M.lastKnownIP]",[is_antagonist],"[REF(M)]")'
>
<b id='search[i]'>[M_name] - [M_rname] - [M_key] ([M_job])</b>
</a>
<br><span id='item[i]'></span>
</td>
</tr>
"}
i++
//player table ending
dat += {"
</table>
</span>
<script type='text/javascript'>
var maintable = document.getElementById("maintable_data_archive");
var complete_list = maintable.innerHTML;
</script>
</body></html>
"}
usr << browse(dat, "window=players;size=600x480")
+754
View File
@@ -0,0 +1,754 @@
/datum/admins/proc/Secrets()
if(!check_rights(0))
return
var/list/dat = list("<B>The first rule of adminbuse is: you don't talk about the adminbuse.</B><HR>")
dat +={"
<B>General Secrets</B><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=admin_log'>Admin Log</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=show_admins'>Show Admin List</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=mentor_log'>Mentor Log</A><BR>
<BR>
"}
if(check_rights(R_ADMIN,0))
dat += {"
<B>Admin Secrets</B><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=clear_virus'>Cure all diseases currently in existence</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=list_bombers'>Bombing List</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=check_antagonist'>Show current traitors and objectives</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=list_signalers'>Show last [length(GLOB.lastsignalers)] signalers</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=list_lawchanges'>Show last [length(GLOB.lawchanges)] law changes</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=showailaws'>Show AI Laws</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=showgm'>Show Game Mode</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=manifest'>Show Crew Manifest</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=DNA'>List DNA (Blood)</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=fingerprints'>List Fingerprints</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=ctfbutton'>Enable/Disable CTF</A><BR><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=tdomereset'>Reset Thunderdome to default state</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=set_name'>Rename Station Name</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=reset_name'>Reset Station Name</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=night_shift_set'>Set Night Shift Mode</A><BR>
<BR>
<B>Shuttles</B><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=moveferry'>Move Ferry</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=togglearrivals'>Toggle Arrivals Ferry</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=moveminingshuttle'>Move Mining Shuttle</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=movelaborshuttle'>Move Labor Shuttle</A><BR>
<BR>
"}
if(check_rights(R_FUN,0))
dat += {"
<B>Fun Secrets</B><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=virus'>Trigger a Virus Outbreak</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=monkey'>Turn all humans into monkeys</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=anime'>Chinese Cartoons</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=allspecies'>Change the species of all humans</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=power'>Make all areas powered</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=unpower'>Make all areas unpowered</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=quickpower'>Power all SMES</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=tripleAI'>Triple AI mode (needs to be used in the lobby)</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=traitor_all'>Everyone is the traitor</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=ak47s'>AK-47s For Everyone!</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=guns'>Summon Guns</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=magic'>Summon Magic</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=events'>Summon Events (Toggle)</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=onlyone'>There can only be one!</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=delayed_onlyone'>There can only be one! (40-second delay)</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=retardify'>Make all players retarded</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=eagles'>Egalitarian Station Mode</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=blackout'>Break all lights</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=whiteout'>Fix all lights</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=floorlava'>The floor is lava! (DANGEROUS: extremely lame)</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=customportal'>Spawn a custom portal storm</A><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=flipmovement'>Flip client movement directions</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=randommovement'>Randomize client movement directions</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=custommovement'>Set each movement direction manually</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=resetmovement'>Reset movement directions to default</A><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=changebombcap'>Change bomb cap</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=masspurrbation'>Mass Purrbation</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=massremovepurrbation'>Mass Remove Purrbation</A><BR>
"}
dat += "<BR>"
if(check_rights(R_DEBUG,0))
dat += {"
<B>Security Level Elevated</B><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=maint_access_engiebrig'>Change all maintenance doors to engie/brig access only</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=maint_access_brig'>Change all maintenance doors to brig access only</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=infinite_sec'>Remove cap on security officers</A><BR>
<BR>
"}
usr << browse(dat.Join(), "window=secrets")
return
/datum/admins/proc/Secrets_topic(item,href_list)
var/datum/round_event/E
var/ok = 0
switch(item)
if("admin_log")
var/dat = "<B>Admin Log<HR></B>"
for(var/l in GLOB.admin_log)
dat += "<li>[l]</li>"
if(!GLOB.admin_log.len)
dat += "No-one has done anything this round!"
usr << browse(dat, "window=admin_log")
if("mentor_log")
CitadelMentorLogSecret()
if("show_admins")
var/dat = "<B>Current admins:</B><HR>"
if(GLOB.admin_datums)
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
dat += "[ckey] - [D.rank.name]<br>"
usr << browse(dat, "window=showadmins;size=600x500")
if("tdomereset")
if(!check_rights(R_ADMIN))
return
var/delete_mobs = alert("Clear all mobs?","Confirm","Yes","No","Cancel")
if(delete_mobs == "Cancel")
return
log_admin("[key_name(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].", 1)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] reset the thunderdome to default with delete_mobs==[delete_mobs].</span>")
var/area/thunderdome = locate(/area/tdome/arena)
if(delete_mobs == "Yes")
for(var/mob/living/mob in thunderdome)
qdel(mob) //Clear mobs
for(var/obj/obj in thunderdome)
if(!istype(obj, /obj/machinery/camera))
qdel(obj) //Clear objects
var/area/template = locate(/area/tdome/arena_source)
template.copy_contents_to(thunderdome)
if("clear_virus")
var/choice = input("Are you sure you want to cure all disease?") in list("Yes", "Cancel")
if(choice == "Yes")
message_admins("[key_name_admin(usr)] has cured all diseases.")
for(var/thing in SSdisease.active_diseases)
var/datum/disease/D = thing
D.cure(0)
if("set_name")
if(!check_rights(R_ADMIN))
return
var/new_name = input(usr, "Please input a new name for the station.", "What?", "") as text|null
if(!new_name)
return
set_station_name(new_name)
log_admin("[key_name(usr)] renamed the station to \"[new_name]\".")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] renamed the station to: [new_name].</span>")
priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
if("night_shift_set")
if(!check_rights(R_ADMIN))
return
var/val = alert(usr, "What do you want to set night shift to? This will override the automatic system until set to automatic again.", "Night Shift", "On", "Off", "Automatic")
switch(val)
if("Automatic")
if(CONFIG_GET(flag/enable_night_shifts))
SSnightshift.can_fire = TRUE
SSnightshift.fire()
else
SSnightshift.update_nightshift(FALSE, TRUE)
if("On")
SSnightshift.can_fire = FALSE
SSnightshift.update_nightshift(TRUE, TRUE)
if("Off")
SSnightshift.can_fire = FALSE
SSnightshift.update_nightshift(FALSE, TRUE)
if("reset_name")
if(!check_rights(R_ADMIN))
return
var/new_name = new_station_name()
set_station_name(new_name)
log_admin("[key_name(usr)] reset the station name.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] reset the station name.</span>")
priority_announce("[command_name()] has renamed the station to \"[new_name]\".")
if("list_bombers")
if(!check_rights(R_ADMIN))
return
var/dat = "<B>Bombing List</B><HR>"
for(var/l in GLOB.bombers)
dat += text("[l]<BR>")
usr << browse(dat, "window=bombers")
if("list_signalers")
if(!check_rights(R_ADMIN))
return
var/dat = "<B>Showing last [length(GLOB.lastsignalers)] signalers.</B><HR>"
for(var/sig in GLOB.lastsignalers)
dat += "[sig]<BR>"
usr << browse(dat, "window=lastsignalers;size=800x500")
if("list_lawchanges")
if(!check_rights(R_ADMIN))
return
var/dat = "<B>Showing last [length(GLOB.lawchanges)] law changes.</B><HR>"
for(var/sig in GLOB.lawchanges)
dat += "[sig]<BR>"
usr << browse(dat, "window=lawchanges;size=800x500")
if("moveminingshuttle")
if(!check_rights(R_ADMIN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Mining Shuttle"))
if(!SSshuttle.toggleShuttle("mining","mining_home","mining_away"))
message_admins("[key_name_admin(usr)] moved mining shuttle")
log_admin("[key_name(usr)] moved the mining shuttle")
if("movelaborshuttle")
if(!check_rights(R_ADMIN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Labor Shuttle"))
if(!SSshuttle.toggleShuttle("laborcamp","laborcamp_home","laborcamp_away"))
message_admins("[key_name_admin(usr)] moved labor shuttle")
log_admin("[key_name(usr)] moved the labor shuttle")
if("moveferry")
if(!check_rights(R_ADMIN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send CentCom Ferry"))
if(!SSshuttle.toggleShuttle("ferry","ferry_home","ferry_away"))
message_admins("[key_name_admin(usr)] moved the CentCom ferry")
log_admin("[key_name(usr)] moved the CentCom ferry")
if("togglearrivals")
if(!check_rights(R_ADMIN))
return
var/obj/docking_port/mobile/arrivals/A = SSshuttle.arrivals
if(A)
var/new_perma = !A.perma_docked
A.perma_docked = new_perma
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Permadock Arrivals Shuttle", "[new_perma ? "Enabled" : "Disabled"]"))
message_admins("[key_name_admin(usr)] [new_perma ? "stopped" : "started"] the arrivals shuttle")
log_admin("[key_name(usr)] [new_perma ? "stopped" : "started"] the arrivals shuttle")
else
to_chat(usr, "<span class='admin'>There is no arrivals shuttle</span>")
if("showailaws")
if(!check_rights(R_ADMIN))
return
output_ai_laws()
if("showgm")
if(!check_rights(R_ADMIN))
return
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
else if (SSticker.mode)
alert("The game mode is [SSticker.mode.name]")
else alert("For some reason there's a SSticker, but not a game mode")
if("manifest")
if(!check_rights(R_ADMIN))
return
var/dat = "<B>Showing Crew Manifest.</B><HR>"
dat += "<table cellspacing=5><tr><th>Name</th><th>Position</th></tr>"
for(var/datum/data/record/t in GLOB.data_core.general)
dat += "<tr><td>[t.fields["name"]]</td><td>[t.fields["rank"]]</td></tr>"
dat += "</table>"
usr << browse(dat, "window=manifest;size=440x410")
if("DNA")
if(!check_rights(R_ADMIN))
return
var/dat = "<B>Showing DNA from blood.</B><HR>"
dat += "<table cellspacing=5><tr><th>Name</th><th>DNA</th><th>Blood Type</th></tr>"
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
if(H.ckey)
dat += "<tr><td>[H]</td><td>[H.dna.unique_enzymes]</td><td>[H.dna.blood_type]</td></tr>"
dat += "</table>"
usr << browse(dat, "window=DNA;size=440x410")
if("fingerprints")
if(!check_rights(R_ADMIN))
return
var/dat = "<B>Showing Fingerprints.</B><HR>"
dat += "<table cellspacing=5><tr><th>Name</th><th>Fingerprints</th></tr>"
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
if(H.ckey)
dat += "<tr><td>[H]</td><td>[md5(H.dna.uni_identity)]</td></tr>"
dat += "</table>"
usr << browse(dat, "window=fingerprints;size=440x410")
if("monkey")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Monkeyize All Humans"))
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
spawn(0)
H.monkeyize()
ok = 1
if("allspecies")
if(!check_rights(R_FUN))
return
var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list
if(result)
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]"))
log_admin("[key_name(usr)] turned all humans into [result]", 1)
message_admins("\blue [key_name_admin(usr)] turned all humans into [result]")
var/newtype = GLOB.species_list[result]
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
H.set_species(newtype)
if("tripleAI")
if(!check_rights(R_FUN))
return
usr.client.triple_ai()
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Triple AI"))
if("power")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All APCs"))
log_admin("[key_name(usr)] made all areas powered", 1)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] made all areas powered</span>")
power_restore()
if("unpower")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Depower All APCs"))
log_admin("[key_name(usr)] made all areas unpowered", 1)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] made all areas unpowered</span>")
power_failure()
if("quickpower")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Power All SMESs"))
log_admin("[key_name(usr)] made all SMESs powered", 1)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] made all SMESs powered</span>")
power_restore_quick()
if("traitor_all")
if(!check_rights(R_FUN))
return
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
return
var/objective = copytext(sanitize(input("Enter an objective")),1,MAX_MESSAGE_LEN)
if(!objective)
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Traitor All", "[objective]"))
for(var/mob/living/H in GLOB.player_list)
if(!(ishuman(H)||istype(H, /mob/living/silicon/)))
continue
if(H.stat == DEAD || !H.client || !H.mind || ispAI(H))
continue
if(is_special_character(H))
continue
var/datum/antagonist/traitor/T = new()
T.give_objectives = FALSE
var/datum/objective/new_objective = new
new_objective.owner = H
new_objective.explanation_text = objective
T.add_objective(new_objective)
H.mind.add_antag_datum(T)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used everyone is a traitor secret. Objective is [objective]</span>")
log_admin("[key_name(usr)] used everyone is a traitor secret. Objective is [objective]")
if("changebombcap")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Bomb Cap"))
var/newBombCap = input(usr,"What would you like the new bomb cap to be. (entered as the light damage range (the 3rd number in common (1,2,3) notation)) Must be above 4)", "New Bomb Cap", GLOB.MAX_EX_LIGHT_RANGE) as num|null
if (!CONFIG_SET(number/bombcap, newBombCap))
return
message_admins("<span class='boldannounce'>[key_name_admin(usr)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]</span>")
log_admin("[key_name(usr)] changed the bomb cap to [GLOB.MAX_EX_DEVESTATION_RANGE], [GLOB.MAX_EX_HEAVY_RANGE], [GLOB.MAX_EX_LIGHT_RANGE]")
if("blackout")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Break All Lights"))
message_admins("[key_name_admin(usr)] broke all lights")
for(var/obj/machinery/light/L in GLOB.machines)
L.break_light_tube()
if("anime")
if(!check_rights(R_FUN))
return
var/animetype = alert("Would you like to have the clothes be changed?",,"Yes","No","Cancel")
var/droptype
if(animetype =="Yes")
droptype = alert("Make the uniforms Nodrop?",,"Yes","No","Cancel")
if(animetype == "Cancel" || droptype == "Cancel")
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Chinese Cartoons"))
message_admins("[key_name_admin(usr)] made everything kawaii.")
for(var/mob/living/carbon/human/H in GLOB.carbon_list)
SEND_SOUND(H, sound('sound/ai/animes.ogg'))
if(H.dna.species.id == "human")
if(H.dna.features["tail_human"] == "None" || H.dna.features["ears"] == "None")
var/obj/item/organ/ears/cat/ears = new
var/obj/item/organ/tail/cat/tail = new
ears.Insert(H, drop_if_replaced=FALSE)
tail.Insert(H, drop_if_replaced=FALSE)
var/list/honorifics = list("[MALE]" = list("kun"), "[FEMALE]" = list("chan","tan"), "[NEUTER]" = list("san")) //John Robust -> Robust-kun
var/list/names = splittext(H.real_name," ")
var/forename = names.len > 1 ? names[2] : names[1]
var/newname = "[forename]-[pick(honorifics["[H.gender]"])]"
H.fully_replace_character_name(H.real_name,newname)
H.update_mutant_bodyparts()
if(animetype == "Yes")
var/seifuku = pick(typesof(/obj/item/clothing/under/schoolgirl))
var/obj/item/clothing/under/schoolgirl/I = new seifuku
var/olduniform = H.w_uniform
H.temporarilyRemoveItemFromInventory(H.w_uniform, TRUE, FALSE)
H.equip_to_slot_or_del(I, SLOT_W_UNIFORM)
qdel(olduniform)
if(droptype == "Yes")
ADD_TRAIT(I, TRAIT_NODROP, ADMIN_TRAIT)
else
to_chat(H, "You're not kawaii enough for this.")
if("whiteout")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Fix All Lights"))
message_admins("[key_name_admin(usr)] fixed all lights")
for(var/obj/machinery/light/L in GLOB.machines)
L.fix()
if("floorlava")
SSweather.run_weather(/datum/weather/floor_is_lava)
if("virus")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Virus Outbreak"))
switch(alert("Do you want this to be a random disease or do you have something in mind?",,"Make Your Own","Random","Choose"))
if("Make Your Own")
AdminCreateVirus(usr.client)
if("Random")
E = new /datum/round_event/disease_outbreak()
if("Choose")
var/virus = input("Choose the virus to spread", "BIOHAZARD") as null|anything in typesof(/datum/disease)
E = new /datum/round_event/disease_outbreak{}()
var/datum/round_event/disease_outbreak/DO = E
DO.virus_type = virus
if("retardify")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Braindamage"))
for(var/mob/living/carbon/human/H in GLOB.player_list)
to_chat(H, "<span class='boldannounce'>You suddenly feel stupid.</span>")
H.adjustBrainLoss(60, 80)
message_admins("[key_name_admin(usr)] made everybody retarded")
if("eagles")//SCRAW
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Egalitarian Station"))
for(var/obj/machinery/door/airlock/W in GLOB.machines)
if(is_station_level(W.z) && !istype(get_area(W), /area/bridge) && !istype(get_area(W), /area/crew_quarters) && !istype(get_area(W), /area/security/prison))
W.req_access = list()
message_admins("[key_name_admin(usr)] activated Egalitarian Station mode")
priority_announce("CentCom airlock control override activated. Please take this time to get acquainted with your coworkers.", null, 'sound/ai/commandreport.ogg')
if("ak47s")
if(!check_rights(R_FUN))
return
message_admins("[key_name_admin(usr)] activated AK-47s for Everyone!")
usr.client.ak47s()
sound_to_playing_players('sound/misc/ak47s.ogg')
if("guns")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Guns"))
var/survivor_probability = 0
switch(alert("Do you want this to create survivors antagonists?",,"No Antags","Some Antags","All Antags!"))
if("Some Antags")
survivor_probability = 25
if("All Antags!")
survivor_probability = 100
rightandwrong(SUMMON_GUNS, usr, survivor_probability)
if("magic")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Magic"))
var/survivor_probability = 0
switch(alert("Do you want this to create survivors antagonists?",,"No Antags","Some Antags","All Antags!"))
if("Some Antags")
survivor_probability = 25
if("All Antags!")
survivor_probability = 100
rightandwrong(SUMMON_MAGIC, usr, survivor_probability)
if("events")
if(!check_rights(R_FUN))
return
if(!SSevents.wizardmode)
if(alert("Do you want to toggle summon events on?",,"Yes","No") == "Yes")
summonevents()
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Events", "Activate"))
else
switch(alert("What would you like to do?",,"Intensify Summon Events","Turn Off Summon Events","Nothing"))
if("Intensify Summon Events")
summonevents()
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Events", "Intensify"))
if("Turn Off Summon Events")
SSevents.toggleWizardmode()
SSevents.resetFrequency()
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Summon Events", "Disable"))
if("dorf")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Dwarf Beards"))
for(var/mob/living/carbon/human/B in GLOB.carbon_list)
B.facial_hair_style = "Dward Beard"
B.update_hair()
message_admins("[key_name_admin(usr)] activated dorf mode")
if("onlyone")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One"))
usr.client.only_one()
sound_to_playing_players('sound/misc/highlander.ogg')
if("delayed_onlyone")
if(!check_rights(R_FUN))
return
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("There Can Be Only One"))
usr.client.only_one_delayed()
sound_to_playing_players('sound/misc/highlander_delayed.ogg')
if("maint_access_brig")
if(!check_rights(R_DEBUG))
return
for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines)
M.check_access()
if (ACCESS_MAINT_TUNNELS in M.req_access)
M.req_access = list(ACCESS_BRIG)
message_admins("[key_name_admin(usr)] made all maint doors brig access-only.")
if("maint_access_engiebrig")
if(!check_rights(R_DEBUG))
return
for(var/obj/machinery/door/airlock/maintenance/M in GLOB.machines)
M.check_access()
if (ACCESS_MAINT_TUNNELS in M.req_access)
M.req_access = list()
M.req_one_access = list(ACCESS_BRIG,ACCESS_ENGINE)
message_admins("[key_name_admin(usr)] made all maint doors engineering and brig access-only.")
if("infinite_sec")
if(!check_rights(R_DEBUG))
return
var/datum/job/J = SSjob.GetJob("Security Officer")
if(!J)
return
J.total_positions = -1
J.spawn_positions = -1
message_admins("[key_name_admin(usr)] has removed the cap on security officers.")
if("ctfbutton")
if(!check_rights(R_ADMIN))
return
toggle_all_ctf(usr)
if("masspurrbation")
if(!check_rights(R_FUN))
return
mass_purrbation()
message_admins("[key_name_admin(usr)] has put everyone on \
purrbation!")
log_admin("[key_name(usr)] has put everyone on purrbation.")
if("massremovepurrbation")
if(!check_rights(R_FUN))
return
mass_remove_purrbation()
message_admins("[key_name_admin(usr)] has removed everyone from \
purrbation.")
log_admin("[key_name(usr)] has removed everyone from purrbation.")
if("flipmovement")
if(!check_rights(R_FUN))
return
if(alert("Flip all movement controls?","Confirm","Yes","Cancel") == "Cancel")
return
var/list/movement_keys = SSinput.movement_keys
for(var/i in 1 to movement_keys.len)
var/key = movement_keys[i]
movement_keys[key] = turn(movement_keys[key], 180)
message_admins("[key_name_admin(usr)] has flipped all movement directions.")
log_admin("[key_name(usr)] has flipped all movement directions.")
if("randommovement")
if(!check_rights(R_FUN))
return
if(alert("Randomize all movement controls?","Confirm","Yes","Cancel") == "Cancel")
return
var/list/movement_keys = SSinput.movement_keys
for(var/i in 1 to movement_keys.len)
var/key = movement_keys[i]
movement_keys[key] = turn(movement_keys[key], 45 * rand(1, 8))
message_admins("[key_name_admin(usr)] has randomized all movement directions.")
log_admin("[key_name(usr)] has randomized all movement directions.")
if("custommovement")
if(!check_rights(R_FUN))
return
if(alert("Are you sure you want to change every movement key?","Confirm","Yes","Cancel") == "Cancel")
return
var/list/movement_keys = SSinput.movement_keys
var/list/new_movement = list()
for(var/i in 1 to movement_keys.len)
var/key = movement_keys[i]
var/msg = "Please input the new movement direction when the user presses [key]. Ex. northeast"
var/title = "New direction for [key]"
var/new_direction = text2dir(input(usr, msg, title) as text|null)
if(!new_direction)
new_direction = movement_keys[key]
new_movement[key] = new_direction
SSinput.movement_keys = new_movement
message_admins("[key_name_admin(usr)] has configured all movement directions.")
log_admin("[key_name(usr)] has configured all movement directions.")
if("resetmovement")
if(!check_rights(R_FUN))
return
if(alert("Are you sure you want to reset movement keys to default?","Confirm","Yes","Cancel") == "Cancel")
return
SSinput.setup_default_movement_keys()
message_admins("[key_name_admin(usr)] has reset all movement keys.")
log_admin("[key_name(usr)] has reset all movement keys.")
if("customportal")
if(!check_rights(R_FUN))
return
var/list/settings = list(
"mainsettings" = list(
"typepath" = list("desc" = "Path to spawn", "type" = "datum", "path" = "/mob/living", "subtypesonly" = TRUE, "value" = /mob/living/simple_animal/hostile/poison/bees),
"humanoutfit" = list("desc" = "Outfit if human", "type" = "datum", "path" = "/datum/outfit", "subtypesonly" = TRUE, "value" = /datum/outfit),
"amount" = list("desc" = "Number per portal", "type" = "number", "value" = 1),
"portalnum" = list("desc" = "Number of total portals", "type" = "number", "value" = 10),
"offerghosts" = list("desc" = "Get ghosts to play mobs", "type" = "boolean", "value" = "No"),
"minplayers" = list("desc" = "Minimum number of ghosts", "type" = "number", "value" = 1),
"playersonly" = list("desc" = "Only spawn ghost-controlled mobs", "type" = "boolean", "value" = "No"),
"ghostpoll" = list("desc" = "Ghost poll question", "type" = "string", "value" = "Do you want to play as %TYPE% portal invader?"),
"delay" = list("desc" = "Time between portals, in deciseconds", "type" = "number", "value" = 50),
"color" = list("desc" = "Portal color", "type" = "color", "value" = "#00FF00"),
"playlightning" = list("desc" = "Play lightning sounds on announcement", "type" = "boolean", "value" = "Yes"),
"announce_players" = list("desc" = "Make an announcement", "type" = "boolean", "value" = "Yes"),
"announcement" = list("desc" = "Announcement", "type" = "string", "value" = "Massive bluespace anomaly detected en route to %STATION%. Brace for impact."),
)
)
message_admins("[key_name(usr)] is creating a custom portal storm...")
var/list/prefreturn = presentpreflikepicker(usr,"Customize Portal Storm", "Customize Portal Storm", Button1="Ok", width = 600, StealFocus = 1,Timeout = 0, settings=settings)
if (prefreturn["button"] == 1)
var/list/prefs = settings["mainsettings"]
if (prefs["amount"]["value"] < 1 || prefs["portalnum"]["value"] < 1)
to_chat(usr, "Number of portals and mobs to spawn must be at least 1")
return
var/mob/pathToSpawn = prefs["typepath"]["value"]
if (!ispath(pathToSpawn))
pathToSpawn = text2path(pathToSpawn)
if (!ispath(pathToSpawn))
to_chat(usr, "Invalid path [pathToSpawn]")
return
var/list/candidates = list()
if (prefs["offerghosts"]["value"] == "Yes")
candidates = pollGhostCandidates(replacetext(prefs["ghostpoll"]["value"], "%TYPE%", initial(pathToSpawn.name)), ROLE_TRAITOR)
if (prefs["playersonly"]["value"] == "Yes" && length(candidates) < prefs["minplayers"]["value"])
message_admins("Not enough players signed up to create a portal storm, the minimum was [prefs["minplayers"]["value"]] and the number of signups [length(candidates)]")
return
if (prefs["announce_players"]["value"] == "Yes")
portalAnnounce(prefs["announcement"]["value"], (prefs["playlightning"]["value"] == "Yes" ? TRUE : FALSE))
var/mutable_appearance/storm = mutable_appearance('icons/obj/tesla_engine/energy_ball.dmi', "energy_ball_fast", FLY_LAYER)
storm.color = prefs["color"]["value"]
message_admins("[key_name_admin(usr)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
log_admin("[key_name(usr)] has created a customized portal storm that will spawn [prefs["portalnum"]["value"]] portals, each of them spawning [prefs["amount"]["value"]] of [pathToSpawn]")
var/outfit = prefs["humanoutfit"]["value"]
if (!ispath(outfit))
outfit = text2path(outfit)
for (var/i in 1 to prefs["portalnum"]["value"])
if (length(candidates)) // if we're spawning players, gotta be a little tricky and also not spawn players on top of NPCs
var/ghostcandidates = list()
for (var/j in 1 to min(prefs["amount"]["value"], length(candidates)))
ghostcandidates += pick_n_take(candidates)
addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, length(ghostcandidates), storm, ghostcandidates, outfit), i*prefs["delay"]["value"])
else if (prefs["playersonly"]["value"] != "Yes")
addtimer(CALLBACK(GLOBAL_PROC, .proc/doPortalSpawn, get_random_station_turf(), pathToSpawn, prefs["amount"]["value"], storm, null, outfit), i*prefs["delay"]["value"])
if(E)
E.processing = FALSE
if(E.announceWhen>0)
if(alert(usr, "Would you like to alert the crew?", "Alert", "Yes", "No") == "No")
E.announceWhen = -1
E.processing = TRUE
if (usr)
log_admin("[key_name(usr)] used secret [item]")
if (ok)
to_chat(world, text("<B>A secret has been activated by []!</B>", usr.key))
/proc/portalAnnounce(announcement, playlightning)
set waitfor = 0
if (playlightning)
sound_to_playing_players('sound/magic/lightning_chargeup.ogg')
sleep(80)
priority_announce(replacetext(announcement, "%STATION%", station_name()))
if (playlightning)
sleep(20)
sound_to_playing_players('sound/magic/lightningbolt.ogg')
/proc/doPortalSpawn(turf/loc, mobtype, numtospawn, portal_appearance, players, humanoutfit)
for (var/i in 1 to numtospawn)
var/mob/spawnedMob = new mobtype(loc)
if (length(players))
var/mob/chosen = players[1]
if (chosen.client)
chosen.client.prefs.copy_to(spawnedMob)
spawnedMob.key = chosen.key
players -= chosen
if (ishuman(spawnedMob) && ispath(humanoutfit, /datum/outfit))
var/mob/living/carbon/human/H = spawnedMob
H.equipOutfit(humanoutfit)
var/turf/T = get_step(loc, SOUTHWEST)
flick_overlay_static(portal_appearance, T, 15)
playsound(T, 'sound/magic/lightningbolt.ogg', rand(80, 100), 1)
+153
View File
@@ -0,0 +1,153 @@
#define SOUND_EMITTER_LOCAL "local" //Plays the sound like a normal heard sound
#define SOUND_EMITTER_DIRECT "direct" //Plays the sound directly to hearers regardless of pressure/proximity/et cetera
#define SOUND_EMITTER_RADIUS "radius" //Plays the sound to everyone in a radius
#define SOUND_EMITTER_ZLEVEL "zlevel" //Plays the sound to everyone on the z-level
#define SOUND_EMITTER_GLOBAL "global" //Plays the sound to everyone in the game world
//Admin sound emitters with highly customizable functions!
/obj/effect/sound_emitter
name = "sound emitter"
desc = "Emits sounds, presumably."
icon = 'icons/effects/effects.dmi'
icon_state = "shield2"
invisibility = INVISIBILITY_OBSERVER
anchored = TRUE
density = FALSE
opacity = FALSE
alpha = 175
var/sound_file //The sound file the emitter plays
var/sound_volume = 50 //The volume the sound file is played at
var/play_radius = 3 //Any mobs within this many tiles will hear the sounds played if it's using the appropriate mode
var/motus_operandi = SOUND_EMITTER_LOCAL //The mode this sound emitter is using
var/emitter_range = SOUND_EMITTER_ZLEVEL //The range this emitter's sound is heard at; this isn't a number, but a string (see the defines above)
/obj/effect/sound_emitter/Destroy(force)
if(!force)
return QDEL_HINT_LETMELIVE
. = ..()
/obj/effect/sound_emitter/singularity_act()
return
/obj/effect/sound_emitter/singularity_pull()
return
/obj/effect/sound_emitter/examine(mob/user)
..()
if(!isobserver(user))
return
to_chat(user, "<span class='boldnotice'>Sound File:</span> [sound_file ? sound_file : "None chosen"]")
to_chat(user, "<span class='boldnotice'>Mode:</span> [motus_operandi]</span>")
to_chat(user, "<span class='boldnotice'>Range:</span> [emitter_range]</span>")
to_chat(user, "<b>Sound is playing at [sound_volume]% volume.</b>")
if(user.client.holder)
to_chat(user, "<b>Alt-click it to quickly activate it!</b>")
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/effect/sound_emitter/attack_ghost(mob/user)
if(!check_rights_for(user.client, R_SOUNDS))
examine(user)
return
edit_emitter(user)
/obj/effect/sound_emitter/AltClick(mob/user)
if(check_rights_for(user.client, R_SOUNDS))
activate(user)
to_chat(user, "<span class='notice'>Sound emitter activated.</span>")
/obj/effect/sound_emitter/proc/edit_emitter(mob/user)
var/dat = ""
dat += "<b>Label:</b> <a href='?src=\ref[src];edit_label=1'>[maptext ? maptext : "No label set!"]</a><br>"
dat += "<br>"
dat += "<b>Sound File:</b> <a href='?src=\ref[src];edit_sound_file=1'>[sound_file ? sound_file : "No file chosen!"]</a><br>"
dat += "<b>Volume:</b> <a href='?src=\ref[src];edit_volume=1'>[sound_volume]%</a><br>"
dat += "<br>"
dat += "<b>Mode:</b> <a href='?src=\ref[src];edit_mode=1'>[motus_operandi]</a><br>"
if(motus_operandi != SOUND_EMITTER_LOCAL)
dat += "<b>Range:</b> <a href='?src=\ref[src];edit_range=1'>[emitter_range]</a>[emitter_range == SOUND_EMITTER_RADIUS ? "<a href='?src=\ref[src];edit_radius=1'>[play_radius]-tile radius</a>" : ""]<br>"
dat += "<br>"
dat += "<a href='?src=\ref[src];play=1'>Play Sound</a> (interrupts other sound emitter sounds)"
var/datum/browser/popup = new(user, "emitter", "", 500, 600)
popup.set_content(dat)
popup.open()
/obj/effect/sound_emitter/Topic(href, href_list)
..()
if(!ismob(usr) || !usr.client || !check_rights_for(usr.client, R_SOUNDS))
return
var/mob/user = usr
if(href_list["edit_label"])
var/new_label = stripped_input(user, "Choose a new label.", "Sound Emitter")
if(!new_label)
return
maptext = new_label
to_chat(user, "<span class='notice'>Label set to [maptext].</span>")
if(href_list["edit_sound_file"])
var/new_file = input(user, "Choose a sound file.", "Sound Emitter") as null|sound
if(!new_file)
return
sound_file = new_file
to_chat(user, "<span class='notice'>New sound file set to [sound_file].</span>")
if(href_list["edit_volume"])
var/new_volume = input(user, "Choose a volume.", "Sound Emitter", sound_volume) as null|num
if(isnull(new_volume))
return
new_volume = CLAMP(new_volume, 0, 100)
sound_volume = new_volume
to_chat(user, "<span class='notice'>Volume set to [sound_volume]%.</span>")
if(href_list["edit_mode"])
var/new_mode
var/mode_list = list("Local (normal sound)" = SOUND_EMITTER_LOCAL, "Direct (not affected by environment/location)" = SOUND_EMITTER_DIRECT)
new_mode = input(user, "Choose a new mode.", "Sound Emitter") as null|anything in mode_list
if(!new_mode)
return
motus_operandi = mode_list[new_mode]
to_chat(user, "<span class='notice'>Mode set to [motus_operandi].</span>")
if(href_list["edit_range"])
var/new_range
var/range_list = list("Radius (all mobs within a radius)" = SOUND_EMITTER_RADIUS, "Z-Level (all mobs on the same z)" = SOUND_EMITTER_ZLEVEL, "Global (all players)" = SOUND_EMITTER_GLOBAL)
new_range = input(user, "Choose a new range.", "Sound Emitter") as null|anything in range_list
if(!new_range)
return
emitter_range = range_list[new_range]
to_chat(user, "<span class='notice'>Range set to [emitter_range].</span>")
if(href_list["edit_radius"])
var/new_radius = input(user, "Choose a radius.", "Sound Emitter", sound_volume) as null|num
if(isnull(new_radius))
return
new_radius = CLAMP(new_radius, 0, 127)
play_radius = new_radius
to_chat(user, "<span class='notice'>Audible radius set to [play_radius].</span>")
if(href_list["play"])
activate(user)
edit_emitter(user) //Refresh the UI to see our changes
/obj/effect/sound_emitter/proc/activate(mob/user)
var/list/hearing_mobs = list()
if(motus_operandi == SOUND_EMITTER_LOCAL)
playsound(src, sound_file, sound_volume, FALSE)
return
switch(emitter_range)
if(SOUND_EMITTER_RADIUS)
for(var/mob/M in GLOB.player_list)
if(get_dist(src, M) <= play_radius)
hearing_mobs += M
if(SOUND_EMITTER_ZLEVEL)
for(var/mob/M in GLOB.player_list)
if(M.z == z)
hearing_mobs += M
if(SOUND_EMITTER_GLOBAL)
hearing_mobs = GLOB.player_list.Copy()
for(var/mob/M in hearing_mobs)
if(M.client.prefs.toggles & SOUND_MIDI)
M.playsound_local(M, sound_file, sound_volume, FALSE, channel = CHANNEL_ADMIN, pressure_affected = FALSE)
if(user)
log_admin("[ADMIN_LOOKUPFLW(user)] activated a sound emitter with file \"[sound_file]\" at [AREACOORD(src)]")
flick("shield1", src)
#undef SOUND_EMITTER_LOCAL
#undef SOUND_EMITTER_DIRECT
#undef SOUND_EMITTER_RADIUS
#undef SOUND_EMITTER_ZLEVEL
#undef SOUND_EMITTER_GLOBAL
+603
View File
@@ -0,0 +1,603 @@
/proc/create_message(type, target_key, admin_ckey, text, timestamp, server, secret, logged = 1, browse, expiry, note_severity)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
if(!type)
return
var/target_ckey = ckey(target_key)
if(!target_key && (type == "note" || type == "message" || type == "watchlist entry"))
var/new_key = input(usr,"Who would you like to create a [type] for?","Enter a key or ckey",null) as null|text
if(!new_key)
return
var/new_ckey = sanitizeSQL(ckey(new_key))
var/datum/DBQuery/query_find_ckey = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ckey = '[new_ckey]'")
if(!query_find_ckey.warn_execute())
qdel(query_find_ckey)
return
if(!query_find_ckey.NextRow())
if(alert(usr, "[new_key]/([new_ckey]) has not been seen before, are you sure you want to create a [type] for them?", "Unknown ckey", "Yes", "No", "Cancel") != "Yes")
qdel(query_find_ckey)
return
qdel(query_find_ckey)
target_ckey = new_ckey
target_key = new_key
if(QDELETED(usr))
return
if(target_ckey)
target_ckey = sanitizeSQL(target_ckey)
if(!target_key)
target_key = target_ckey
if(!admin_ckey)
admin_ckey = usr.ckey
if(!admin_ckey)
return
admin_ckey = sanitizeSQL(admin_ckey)
if(!target_ckey)
target_ckey = admin_ckey
if(!text)
text = input(usr,"Write your [type]","Create [type]") as null|message
if(!text)
return
text = sanitizeSQL(text)
if(!timestamp)
timestamp = SQLtime()
if(!server)
var/ssqlname = CONFIG_GET(string/serversqlname)
if (ssqlname)
server = ssqlname
server = sanitizeSQL(server)
if(isnull(secret))
switch(alert("Hide note from being viewed by players?", "Secret note?","Yes","No","Cancel"))
if("Yes")
secret = 1
if("No")
secret = 0
else
return
if(isnull(expiry))
if(alert(usr, "Set an expiry time? Expired messages are hidden like deleted ones.", "Expiry time?", "Yes", "No", "Cancel") == "Yes")
var/expire_time = input("Set expiry time for [type] as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than current time for obvious reasons.", "Set expiry time", SQLtime()) as null|text
if(!expire_time)
return
expire_time = sanitizeSQL(expire_time)
var/datum/DBQuery/query_validate_expire_time = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[expire_time]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[expire_time]','%Y-%c-%d %T'), 0)")
if(!query_validate_expire_time.warn_execute())
qdel(query_validate_expire_time)
return
if(query_validate_expire_time.NextRow())
var/checktime = text2num(query_validate_expire_time.item[1])
if(!checktime)
to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.")
qdel(query_validate_expire_time)
return
expiry = query_validate_expire_time.item[1]
qdel(query_validate_expire_time)
if(type == "note" && isnull(note_severity))
note_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("High", "Medium", "Minor", "None")
if(!note_severity)
return
note_severity = sanitizeSQL(note_severity)
var/datum/DBQuery/query_create_message = SSdbcore.NewQuery("INSERT INTO [format_table_name("messages")] (type, targetckey, adminckey, text, timestamp, server, server_ip, server_port, round_id, secret, expire_timestamp, severity) VALUES ('[type]', '[target_ckey]', '[admin_ckey]', '[text]', '[timestamp]', '[server]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[GLOB.round_id]','[secret]', [expiry ? "'[expiry]'" : "NULL"], [note_severity ? "'[note_severity]'" : "NULL"])")
var/pm = "[key_name(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""]: [text]"
var/header = "[key_name_admin(usr)] has created a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""]"
if(!query_create_message.warn_execute())
qdel(query_create_message)
return
qdel(query_create_message)
if(logged)
log_admin_private(pm)
message_admins("[header]:<br>[text]")
admin_ticket_log(target_ckey, "<font color='blue'>[header]</font>")
admin_ticket_log(target_ckey, text)
if(browse)
browse_messages("[type]")
else
browse_messages(target_ckey = target_ckey, agegate = TRUE)
/proc/delete_message(message_id, logged = 1, browse)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
message_id = text2num(message_id)
if(!message_id)
return
var/type
var/target_key
var/text
var/user_key_name = key_name(usr)
var/user_name_admin = key_name_admin(usr)
var/datum/DBQuery/query_find_del_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), text FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0")
if(!query_find_del_message.warn_execute())
qdel(query_find_del_message)
return
if(query_find_del_message.NextRow())
type = query_find_del_message.item[1]
target_key = query_find_del_message.item[2]
text = query_find_del_message.item[3]
qdel(query_find_del_message)
var/datum/DBQuery/query_del_message = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET deleted = 1 WHERE id = [message_id]")
if(!query_del_message.warn_execute())
qdel(query_del_message)
return
qdel(query_del_message)
if(logged)
var/m1 = "[user_key_name] has deleted a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for" : " made by"] [target_key]: [text]"
var/m2 = "[user_name_admin] has deleted a [type][(type == "note" || type == "message" || type == "watchlist entry") ? " for" : " made by"] [target_key]:<br>[text]"
log_admin_private(m1)
message_admins(m2)
if(browse)
browse_messages("[type]")
else
browse_messages(target_ckey = ckey(target_key), agegate = TRUE)
/proc/edit_message(message_id, browse)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
message_id = text2num(message_id)
if(!message_id)
return
var/editor_ckey = sanitizeSQL(usr.ckey)
var/editor_key = sanitizeSQL(usr.key)
var/kn = key_name(usr)
var/kna = key_name_admin(usr)
var/datum/DBQuery/query_find_edit_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), text FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0")
if(!query_find_edit_message.warn_execute())
qdel(query_find_edit_message)
return
if(query_find_edit_message.NextRow())
var/type = query_find_edit_message.item[1]
var/target_key = query_find_edit_message.item[2]
var/admin_key = query_find_edit_message.item[3]
var/old_text = query_find_edit_message.item[4]
var/new_text = input("Input new [type]", "New [type]", "[old_text]") as null|message
if(!new_text)
qdel(query_find_edit_message)
return
new_text = sanitizeSQL(new_text)
var/edit_text = sanitizeSQL("Edited by [editor_key] on [SQLtime()] from<br>[old_text]<br>to<br>[new_text]<hr>")
var/datum/DBQuery/query_edit_message = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET text = '[new_text]', lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0")
if(!query_edit_message.warn_execute())
qdel(query_edit_message)
return
qdel(query_edit_message)
log_admin_private("[kn] has edited a [type] [(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""] made by [admin_key] from [old_text] to [new_text]")
message_admins("[kna] has edited a [type] [(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""] made by [admin_key] from<br>[old_text]<br>to<br>[new_text]")
if(browse)
browse_messages("[type]")
else
browse_messages(target_ckey = ckey(target_key), agegate = TRUE)
qdel(query_find_edit_message)
/proc/edit_message_expiry(message_id, browse)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
message_id = text2num(message_id)
if(!message_id)
return
var/editor_ckey = sanitizeSQL(usr.ckey)
var/editor_key = sanitizeSQL(usr.key)
var/kn = key_name(usr)
var/kna = key_name_admin(usr)
var/datum/DBQuery/query_find_edit_expiry_message = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), expire_timestamp FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0")
if(!query_find_edit_expiry_message.warn_execute())
qdel(query_find_edit_expiry_message)
return
if(query_find_edit_expiry_message.NextRow())
var/type = query_find_edit_expiry_message.item[1]
var/target_key = query_find_edit_expiry_message.item[2]
var/admin_key = query_find_edit_expiry_message.item[3]
var/old_expiry = query_find_edit_expiry_message.item[4]
var/new_expiry
var/expire_time = input("Set expiry time for [type] as format YYYY-MM-DD HH:MM:SS. All times in server time. HH:MM:SS is optional and 24-hour. Must be later than current time for obvious reasons. Enter -1 to remove expiry time.", "Set expiry time", old_expiry) as null|text
if(!expire_time)
qdel(query_find_edit_expiry_message)
return
if(expire_time == "-1")
new_expiry = "non-expiring"
else
expire_time = sanitizeSQL(expire_time)
var/datum/DBQuery/query_validate_expire_time_edit = SSdbcore.NewQuery("SELECT IF(STR_TO_DATE('[expire_time]','%Y-%c-%d %T') > NOW(), STR_TO_DATE('[expire_time]','%Y-%c-%d %T'), 0)")
if(!query_validate_expire_time_edit.warn_execute())
qdel(query_validate_expire_time_edit)
qdel(query_find_edit_expiry_message)
return
if(query_validate_expire_time_edit.NextRow())
var/checktime = text2num(query_validate_expire_time_edit.item[1])
if(!checktime)
to_chat(usr, "Datetime entered is improperly formatted or not later than current server time.")
qdel(query_validate_expire_time_edit)
qdel(query_find_edit_expiry_message)
return
new_expiry = query_validate_expire_time_edit.item[1]
qdel(query_validate_expire_time_edit)
var/edit_text = sanitizeSQL("Expiration time edited by [editor_key] on [SQLtime()] from [old_expiry] to [new_expiry]<hr>")
var/datum/DBQuery/query_edit_message_expiry = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET expire_timestamp = [expire_time == "-1" ? "NULL" : "'[new_expiry]'"], lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0")
if(!query_edit_message_expiry.warn_execute())
qdel(query_edit_message_expiry)
qdel(query_find_edit_expiry_message)
return
qdel(query_edit_message_expiry)
log_admin_private("[kn] has edited the expiration time of a [type] [(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""] made by [admin_key] from [old_expiry] to [new_expiry]")
message_admins("[kna] has edited the expiration time of a [type] [(type == "note" || type == "message" || type == "watchlist entry") ? " for [target_key]" : ""] made by [admin_key] from [old_expiry] to [new_expiry]")
if(browse)
browse_messages("[type]")
else
browse_messages(target_ckey = ckey(target_key), agegate = TRUE)
qdel(query_find_edit_expiry_message)
/proc/edit_message_severity(message_id)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
message_id = text2num(message_id)
if(!message_id)
return
var/kn = key_name(usr)
var/kna = key_name_admin(usr)
var/datum/DBQuery/query_find_edit_note_severity = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), severity FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0")
if(!query_find_edit_note_severity.warn_execute())
qdel(query_find_edit_note_severity)
return
if(query_find_edit_note_severity.NextRow())
var/type = query_find_edit_note_severity.item[1]
var/target_key = query_find_edit_note_severity.item[2]
var/admin_key = query_find_edit_note_severity.item[3]
var/old_severity = query_find_edit_note_severity.item[4]
if(!old_severity)
old_severity = "NA"
var/editor_key = sanitizeSQL(usr.key)
var/editor_ckey = sanitizeSQL(usr.ckey)
var/new_severity = input("Set the severity of the note.", "Severity", null, null) as null|anything in list("high", "medium", "minor", "none") //lowercase for edit log consistency
if(!new_severity)
qdel(query_find_edit_note_severity)
return
new_severity = sanitizeSQL(new_severity)
var/edit_text = sanitizeSQL("Note severity edited by [editor_key] on [SQLtime()] from [old_severity] to [new_severity]<hr>")
var/datum/DBQuery/query_edit_note_severity = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET severity = '[new_severity]', lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id] AND deleted = 0")
if(!query_edit_note_severity.warn_execute(async = TRUE))
qdel(query_edit_note_severity)
qdel(qdel(query_find_edit_note_severity))
return
qdel(query_edit_note_severity)
log_admin_private("[kn] has edited the severity of a [type] for [target_key] made by [admin_key] from [old_severity] to [new_severity]")
message_admins("[kna] has edited the severity time of a [type] for [target_key] made by [admin_key] from [old_severity] to [new_severity]")
browse_messages(target_ckey = ckey(target_key), agegate = TRUE)
qdel(query_find_edit_note_severity)
/proc/toggle_message_secrecy(message_id)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
message_id = text2num(message_id)
if(!message_id)
return
var/editor_ckey = sanitizeSQL(usr.ckey)
var/editor_key = sanitizeSQL(usr.key)
var/kn = key_name(usr)
var/kna = key_name_admin(usr)
var/datum/DBQuery/query_find_message_secret = SSdbcore.NewQuery("SELECT type, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), targetckey), secret FROM [format_table_name("messages")] WHERE id = [message_id] AND deleted = 0")
if(!query_find_message_secret.warn_execute())
qdel(query_find_message_secret)
return
if(query_find_message_secret.NextRow())
var/type = query_find_message_secret.item[1]
var/target_key = query_find_message_secret.item[2]
var/admin_key = query_find_message_secret.item[3]
var/secret = text2num(query_find_message_secret.item[4])
var/edit_text = "Made [secret ? "not secret" : "secret"] by [editor_key] on [SQLtime()]<hr>"
var/datum/DBQuery/query_message_secret = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET secret = NOT secret, lasteditor = '[editor_ckey]', edits = CONCAT(IFNULL(edits,''),'[edit_text]') WHERE id = [message_id]")
if(!query_message_secret.warn_execute())
qdel(query_find_message_secret)
qdel(query_message_secret)
return
qdel(query_message_secret)
log_admin_private("[kn] has toggled [target_key]'s [type] made by [admin_key] to [secret ? "not secret" : "secret"]")
message_admins("[kna] has toggled [target_key]'s [type] made by [admin_key] to [secret ? "not secret" : "secret"]")
browse_messages(target_ckey = ckey(target_key), agegate = TRUE)
qdel(query_find_message_secret)
/proc/browse_messages(type, target_ckey, index, linkless = FALSE, filter, agegate = FALSE)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
var/list/output = list()
var/ruler = "<hr style='background:#000000; border:0; height:3px'>"
var/list/navbar = list("<a href='?_src_=holder;[HrefToken()];nonalpha=1'>All</a><a href='?_src_=holder;[HrefToken()];nonalpha=2'>#</a>")
for(var/letter in GLOB.alphabet)
navbar += "<a href='?_src_=holder;[HrefToken()];showmessages=[letter]'>[letter]</a>"
navbar += "<a href='?_src_=holder;[HrefToken()];showmemo=1'>Memos</a><a href='?_src_=holder;[HrefToken()];showwatch=1'>Watchlist</a>"
navbar += "<br><form method='GET' name='search' action='?'>\
<input type='hidden' name='_src_' value='holder'>\
[HrefTokenFormField()]\
<input type='text' name='searchmessages' value='[index]'>\
<input type='submit' value='Search'></form>"
if(!linkless)
output = navbar
if(type == "memo" || type == "watchlist entry")
if(type == "memo")
output += "<h2><center>Admin memos</h2>"
output += "<a href='?_src_=holder;[HrefToken()];addmemo=1'>Add memo</a></center>"
else if(type == "watchlist entry")
output += "<h2><center>Watchlist entries</h2>"
output += "<a href='?_src_=holder;[HrefToken()];addwatchempty=1'>Add watchlist entry</a>"
if(filter)
output += "<a href='?_src_=holder;[HrefToken()];showwatch=1'>Unfilter clients</a></center>"
else
output += "<a href='?_src_=holder;[HrefToken()];showwatchfilter=1'>Filter offline clients</a></center>"
output += ruler
var/datum/DBQuery/query_get_type_messages = SSdbcore.NewQuery("SELECT id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), targetckey, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, server, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), expire_timestamp FROM [format_table_name("messages")] WHERE type = '[type]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)")
if(!query_get_type_messages.warn_execute())
qdel(query_get_type_messages)
return
while(query_get_type_messages.NextRow())
if(QDELETED(usr))
return
var/id = query_get_type_messages.item[1]
var/t_key = query_get_type_messages.item[2]
var/t_ckey = query_get_type_messages.item[3]
if(type == "watchlist entry" && filter && !(t_ckey in GLOB.directory))
continue
var/admin_key = query_get_type_messages.item[4]
var/text = query_get_type_messages.item[5]
var/timestamp = query_get_type_messages.item[6]
var/server = query_get_type_messages.item[7]
var/editor_key = query_get_type_messages.item[8]
var/expire_timestamp = query_get_type_messages.item[9]
output += "<b>"
if(type == "watchlist entry")
output += "[t_key] | "
output += "[timestamp] | [server] | [admin_key]"
if(expire_timestamp)
output += " | Expires [expire_timestamp]"
output += "</b>"
output += " <a href='?_src_=holder;[HrefToken()];editmessageexpiryempty=[id]'>Change Expiry Time</a>"
output += " <a href='?_src_=holder;[HrefToken()];deletemessageempty=[id]'>Delete</a>"
output += " <a href='?_src_=holder;[HrefToken()];editmessageempty=[id]'>Edit</a>"
if(editor_key)
output += " <font size='2'>Last edit by [editor_key] <a href='?_src_=holder;[HrefToken()];messageedits=[id]'>(Click here to see edit log)</a></font>"
output += "<br>[text]<hr style='background:#000000; border:0; height:1px'>"
qdel(query_get_type_messages)
if(target_ckey)
target_ckey = sanitizeSQL(target_ckey)
var/target_key
var/datum/DBQuery/query_get_messages = SSdbcore.NewQuery("SELECT type, secret, id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, server, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor), DATEDIFF(NOW(), timestamp), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey), targetckey), expire_timestamp, severity FROM [format_table_name("messages")] WHERE type <> 'memo' AND targetckey = '[target_ckey]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY timestamp DESC")
if(!query_get_messages.warn_execute())
qdel(query_get_messages)
return
var/list/messagedata = list()
var/list/watchdata = list()
var/list/notedata = list()
var/skipped = 0
while(query_get_messages.NextRow())
if(QDELETED(usr))
return
type = query_get_messages.item[1]
if(type == "memo")
continue
var/secret = text2num(query_get_messages.item[2])
if(linkless && secret)
continue
var/id = query_get_messages.item[3]
var/admin_key = query_get_messages.item[4]
var/text = query_get_messages.item[5]
var/timestamp = query_get_messages.item[6]
var/server = query_get_messages.item[7]
var/editor_key = query_get_messages.item[8]
var/age = text2num(query_get_messages.item[9])
target_key = query_get_messages.item[10]
var/expire_timestamp = query_get_messages.item[11]
var/severity = query_get_messages.item[12]
var/alphatext = ""
var/nsd = CONFIG_GET(number/note_stale_days)
var/nfd = CONFIG_GET(number/note_fresh_days)
if (agegate && type == "note" && isnum(nsd) && isnum(nfd) && nsd > nfd)
var/alpha = CLAMP(100 - (age - nfd) * (85 / (nsd - nfd)), 15, 100)
if (alpha < 100)
if (alpha <= 15)
if (skipped)
skipped++
continue
alpha = 10
skipped = TRUE
alphatext = "filter: alpha(opacity=[alpha]); opacity: [alpha/100];"
var/list/data = list("<div style='margin:0px;[alphatext]'><p class='severity'>")
if(severity)
data += "<img src='[severity]_button.png' height='24' width='24'></img> "
data += "<b>[timestamp] | [server] | [admin_key][secret ? " | <i>- Secret</i>" : ""]"
if(expire_timestamp)
data += " | Expires [expire_timestamp]"
data += "</b></p><center>"
if(!linkless)
if(type == "note")
if(severity)
data += "<a href='?_src_=holder;[HrefToken()];editmessageseverity=[id]'>[severity=="none" ? "No" : "[capitalize(severity)]"] Severity</a>"
else
data += "<a href='?_src_=holder;[HrefToken()];editmessageseverity=[id]'>N/A Severity</a>"
data += " <a href='?_src_=holder;[HrefToken()];editmessageexpiry=[id]'>Change Expiry Time</a>"
data += " <a href='?_src_=holder;[HrefToken()];deletemessage=[id]'>Delete</a>"
if(type == "note")
data += " <a href='?_src_=holder;[HrefToken()];secretmessage=[id]'>[secret ? "<b>Secret</b>" : "Not secret"]</a>"
if(type == "message sent")
data += " <font size='2'>Message has been sent</font>"
if(editor_key)
data += "|"
else
data += " <a href='?_src_=holder;[HrefToken()];editmessage=[id]'>Edit</a>"
if(editor_key)
data += " <font size='2'>Last edit by [editor_key] <a href='?_src_=holder;[HrefToken()];messageedits=[id]'>(Click here to see edit log)</a></font>"
data += "</div></center>"
data += "<p style='[alphatext]'>[text]</p><hr style='background:#000000; border:0; height:1px; [alphatext]'>"
switch(type)
if("message")
messagedata += data
if("message sent")
messagedata += data
if("watchlist entry")
watchdata += data
if("note")
notedata += data
qdel(query_get_messages)
if(!target_key)
var/datum/DBQuery/query_get_message_key = SSdbcore.NewQuery("SELECT byond_key FROM [format_table_name("player")] WHERE ckey = '[target_ckey]'")
if(!query_get_message_key.warn_execute())
qdel(query_get_message_key)
return
if(query_get_message_key.NextRow())
target_key = query_get_message_key.item[1]
qdel(query_get_message_key)
output += "<h2><center>[target_key]</center></h2><center>"
if(!linkless)
output += "<a href='?_src_=holder;[HrefToken()];addnote=[target_key]'>Add note</a>"
output += " <a href='?_src_=holder;[HrefToken()];addmessage=[target_key]'>Add message</a>"
output += " <a href='?_src_=holder;[HrefToken()];addwatch=[target_key]'>Add to watchlist</a>"
output += " <a href='?_src_=holder;[HrefToken()];showmessageckey=[target_ckey]'>Refresh page</a></center>"
else
output += " <a href='?_src_=holder;[HrefToken()];showmessageckeylinkless=[target_ckey]'>Refresh page</a></center>"
output += ruler
if(messagedata)
output += "<h2>Messages</h2>"
output += messagedata
if(watchdata)
output += "<h2>Watchlist</h2>"
output += watchdata
if(notedata)
output += "<h2>Notes</h2>"
output += notedata
if(!linkless)
if (agegate)
if (skipped) //the first skipped message is still shown so that we can put this link over it.
output += "<center><a href='?_src_=holder;[HrefToken()];showmessageckey=[target_ckey];showall=1' style='position: relative; top: -3em;'>Show [skipped] hidden messages</a></center>"
else
output += "<center><a href='?_src_=holder;[HrefToken()];showmessageckey=[target_ckey];showall=1'>Show All</a></center>"
else
output += "<center><a href='?_src_=holder;[HrefToken()];showmessageckey=[target_ckey]'>Hide Old</a></center>"
if(index)
var/search
output += "<center><a href='?_src_=holder;[HrefToken()];addmessageempty=1'>Add message</a><a href='?_src_=holder;[HrefToken()];addwatchempty=1'>Add watchlist entry</a><a href='?_src_=holder;[HrefToken()];addnoteempty=1'>Add note</a></center>"
output += ruler
if(!isnum(index))
index = sanitizeSQL(index)
switch(index)
if(1)
search = "^."
if(2)
search = "^\[^\[:alpha:\]\]"
else
search = "^[index]"
var/datum/DBQuery/query_list_messages = SSdbcore.NewQuery("SELECT DISTINCT targetckey, (SELECT byond_key FROM [format_table_name("player")] WHERE ckey = targetckey) FROM [format_table_name("messages")] WHERE type <> 'memo' AND targetckey REGEXP '[search]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL) ORDER BY targetckey")
if(!query_list_messages.warn_execute())
qdel(query_list_messages)
return
while(query_list_messages.NextRow())
if(QDELETED(usr))
return
var/index_ckey = query_list_messages.item[1]
var/index_key = query_list_messages.item[2]
if(!index_key)
index_key = index_ckey
output += "<a href='?_src_=holder;[HrefToken()];showmessageckey=[index_ckey]'>[index_key]</a><br>"
qdel(query_list_messages)
else if(!type && !target_ckey && !index)
output += "<center><a href='?_src_=holder;[HrefToken()];addmessageempty=1'>Add message</a><a href='?_src_=holder;[HrefToken()];addwatchempty=1'>Add watchlist entry</a><a href='?_src_=holder;[HrefToken()];addnoteempty=1'>Add note</a></center>"
output += ruler
var/datum/browser/browser = new(usr, "Note panel", "Manage player notes", 1000, 500)
var/datum/asset/notes_assets = get_asset_datum(/datum/asset/simple/notes)
notes_assets.send(src)
browser.set_content(jointext(output, ""))
browser.open()
/proc/get_message_output(type, target_ckey)
if(!SSdbcore.Connect())
to_chat(usr, "<span class='danger'>Failed to establish database connection.</span>")
return
if(!type)
return
var/output
if(target_ckey)
target_ckey = sanitizeSQL(target_ckey)
var/query = "SELECT id, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = adminckey), adminckey), text, timestamp, IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE ckey = lasteditor), lasteditor) FROM [format_table_name("messages")] WHERE type = '[type]' AND deleted = 0 AND (expire_timestamp > NOW() OR expire_timestamp IS NULL)"
if(type == "message" || type == "watchlist entry")
query += " AND targetckey = '[target_ckey]'"
var/datum/DBQuery/query_get_message_output = SSdbcore.NewQuery(query)
if(!query_get_message_output.warn_execute())
qdel(query_get_message_output)
return
while(query_get_message_output.NextRow())
var/message_id = query_get_message_output.item[1]
var/admin_key = query_get_message_output.item[2]
var/text = query_get_message_output.item[3]
var/timestamp = query_get_message_output.item[4]
var/editor_key = query_get_message_output.item[5]
switch(type)
if("message")
output += "<font color='red' size='3'><b>Admin message left by <span class='prefix'>[admin_key]</span> on [timestamp]</b></font>"
output += "<br><font color='red'>[text]</font><br>"
var/datum/DBQuery/query_message_read = SSdbcore.NewQuery("UPDATE [format_table_name("messages")] SET type = 'message sent' WHERE id = [message_id]")
if(!query_message_read.warn_execute())
qdel(query_get_message_output)
qdel(query_message_read)
return
qdel(query_message_read)
if("watchlist entry")
message_admins("<font color='red'><B>Notice: </B></font><font color='blue'>[key_name_admin(target_ckey)] has been on the watchlist since [timestamp] and has just connected - Reason: [text]</font>")
send2irc_adminless_only("Watchlist", "[key_name(target_ckey)] is on the watchlist and has just connected - Reason: [text]")
if("memo")
output += "<span class='memo'>Memo by <span class='prefix'>[admin_key]</span> on [timestamp]"
if(editor_key)
output += "<br><span class='memoedit'>Last edit by [editor_key] <A href='?_src_=holder;[HrefToken()];messageedits=[message_id]'>(Click here to see edit log)</A></span>"
output += "<br>[text]</span><br>"
qdel(query_get_message_output)
return output
#define NOTESFILE "data/player_notes.sav"
//if the AUTOCONVERT_NOTES is turned on, anytime a player connects this will be run to try and add all their notes to the databas
/proc/convert_notes_sql(ckey)
if(!fexists(NOTESFILE))
return
var/savefile/notesfile = new(NOTESFILE)
if(!notesfile)
log_game("Error: Cannot access [NOTESFILE]")
return
notesfile.cd = "/[ckey]"
while(!notesfile.eof)
var/notetext
notesfile >> notetext
var/server
var/ssqlname = CONFIG_GET(string/serversqlname)
if (ssqlname)
server = ssqlname
var/regex/note = new("^(\\d{2}-\\w{3}-\\d{4}) \\| (.+) ~(\\w+)$", "i")
note.Find(notetext)
var/timestamp = note.group[1]
notetext = note.group[2]
var/admin_ckey = note.group[3]
var/datum/DBQuery/query_convert_time = SSdbcore.NewQuery("SELECT ADDTIME(STR_TO_DATE('[timestamp]','%d-%b-%Y'), '0')")
if(!query_convert_time.Execute())
qdel(query_convert_time)
return
if(query_convert_time.NextRow())
timestamp = query_convert_time.item[1]
qdel(query_convert_time)
if(ckey && notetext && timestamp && admin_ckey && server)
create_message("note", ckey, admin_ckey, notetext, timestamp, server, 1, 0, null, 0, 0)
notesfile.cd = "/"
notesfile.dir.Remove(ckey)
/*alternatively this proc can be run once to pass through every note and attempt to convert it before deleting the file, if done then AUTOCONVERT_NOTES should be turned off
this proc can take several minutes to execute fully if converting and cause DD to hang if converting a lot of notes; it's not advised to do so while a server is live
/proc/mass_convert_notes()
to_chat(world, "Beginning mass note conversion")
var/savefile/notesfile = new(NOTESFILE)
if(!notesfile)
log_game("Error: Cannot access [NOTESFILE]")
return
notesfile.cd = "/"
for(var/ckey in notesfile.dir)
convert_notes_sql(ckey)
to_chat(world, "Deleting NOTESFILE")
fdel(NOTESFILE)
to_chat(world, "Finished mass note conversion, remember to turn off AUTOCONVERT_NOTES")*/
#undef NOTESFILE
+248
View File
@@ -0,0 +1,248 @@
/datum/admins/proc/stickyban(action,data)
if(!check_rights(R_BAN))
return
switch (action)
if ("show")
stickyban_show()
if ("add")
var/list/ban = list()
var/ckey
ban["admin"] = usr.key
ban["type"] = list("sticky")
ban["reason"] = "(InGameBan)([usr.key])" //this will be displayed in dd only
if (data["ckey"])
ckey = ckey(data["ckey"])
else
ckey = input(usr,"Ckey","Ckey","") as text|null
if (!ckey)
return
ckey = ckey(ckey)
ban["ckey"] = ckey
if (get_stickyban_from_ckey(ckey))
to_chat(usr, "<span class='adminnotice'>Error: Can not add a stickyban: User already has a current sticky ban</span>")
if (data["reason"])
ban["message"] = data["reason"]
else
var/reason = input(usr,"Reason","Reason","Ban Evasion") as text|null
if (!reason)
return
ban["message"] = "[reason]"
world.SetConfig("ban",ckey,list2stickyban(ban))
log_admin_private("[key_name(usr)] has stickybanned [ckey].\nReason: [ban["message"]]")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has stickybanned [ckey].\nReason: [ban["message"]]</span>")
if ("remove")
if (!data["ckey"])
return
var/ckey = data["ckey"]
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
if (alert("Are you sure you want to remove the sticky ban on [ckey]?","Are you sure","Yes","No") == "No")
return
if (!get_stickyban_from_ckey(ckey))
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
world.SetConfig("ban",ckey, null)
log_admin_private("[key_name(usr)] removed [ckey]'s stickyban")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] removed [ckey]'s stickyban</span>")
if ("remove_alt")
if (!data["ckey"])
return
var/ckey = data["ckey"]
if (!data["alt"])
return
var/alt = ckey(data["alt"])
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/found = 0
//we have to do it this way because byond keeps the case in its sticky ban matches WHY!!!
for (var/key in ban["keys"])
if (ckey(key) == alt)
found = 1
break
if (!found)
to_chat(usr, "<span class='adminnotice'>Error: [alt] is not linked to [ckey]'s sticky ban!</span>")
return
if (alert("Are you sure you want to disassociate [alt] from [ckey]'s sticky ban? \nNote: Nothing stops byond from re-linking them","Are you sure","Yes","No") == "No")
return
//we have to do this again incase something changes
ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
found = 0
for (var/key in ban["keys"])
if (ckey(key) == alt)
ban["keys"] -= key
found = 1
break
if (!found)
to_chat(usr, "<span class='adminnotice'>Error: [alt] link to [ckey]'s sticky ban disappeared.</span>")
return
world.SetConfig("ban",ckey,list2stickyban(ban))
log_admin_private("[key_name(usr)] has disassociated [alt] from [ckey]'s sticky ban")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has disassociated [alt] from [ckey]'s sticky ban</span>")
if ("edit")
if (!data["ckey"])
return
var/ckey = data["ckey"]
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/oldreason = ban["message"]
var/reason = input(usr,"Reason","Reason","[ban["message"]]") as text|null
if (!reason || reason == oldreason)
return
//we have to do this again incase something changed while we waited for input
ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: The ban disappeared.</span>")
return
ban["message"] = "[reason]"
world.SetConfig("ban",ckey,list2stickyban(ban))
log_admin_private("[key_name(usr)] has edited [ckey]'s sticky ban reason from [oldreason] to [reason]")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has edited [ckey]'s sticky ban reason from [oldreason] to [reason]</span>")
if ("revert")
if (!data["ckey"])
return
var/ckey = data["ckey"]
if (alert("Are you sure you want to revert the sticky ban on [ckey] to its state at round start?","Are you sure","Yes","No") == "No")
return
var/ban = get_stickyban_from_ckey(ckey)
if (!ban)
to_chat(usr, "<span class='adminnotice'>Error: No sticky ban for [ckey] found!</span>")
return
var/cached_ban = SSstickyban.cache[ckey]
if (!cached_ban)
to_chat(usr, "<span class='adminnotice'>Error: No cached sticky ban for [ckey] found!</span>")
world.SetConfig("ban",ckey,null)
log_admin_private("[key_name(usr)] has reverted [ckey]'s sticky ban to its state at round start.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has reverted [ckey]'s sticky ban to its state at round start.</span>")
//revert is mostly used when shit goes rouge, so we have to set it to null
// and wait a byond tick before assigning it to ensure byond clears its shit.
sleep(world.tick_lag)
world.SetConfig("ban",ckey,list2stickyban(cached_ban))
/datum/admins/proc/stickyban_gethtml(ckey, ban)
. = {"
<a href='?_src_=holder;[HrefToken()];stickyban=remove&ckey=[ckey]'>\[-\]</a>
<a href='?_src_=holder;[HrefToken()];stickyban=revert&ckey=[ckey]'>\[revert\]</a>
<b>[ckey]</b>
<br />"
[ban["message"]] <b><a href='?_src_=holder;[HrefToken()];stickyban=edit&ckey=[ckey]'>\[Edit\]</a></b><br />
"}
if (ban["admin"])
. += "[ban["admin"]]<br />"
else
. += "LEGACY<br />"
. += "Caught keys<br />\n<ol>"
for (var/key in ban["keys"])
if (ckey(key) == ckey)
continue
. += "<li><a href='?_src_=holder;[HrefToken()];stickyban=remove_alt&ckey=[ckey]&alt=[ckey(key)]'>\[-\]</a>[key]</li>"
. += "</ol>\n"
/datum/admins/proc/stickyban_show()
if(!check_rights(R_BAN))
return
var/list/bans = sortList(world.GetConfig("ban"))
var/banhtml = ""
for(var/key in bans)
var/ckey = ckey(key)
var/ban = stickyban2list(world.GetConfig("ban",key))
banhtml += "<br /><hr />\n"
banhtml += stickyban_gethtml(ckey,ban)
var/html = {"
<head>
<title>Sticky Bans</title>
</head>
<body>
<h2>All Sticky Bans:</h2> <a href='?_src_=holder;[HrefToken()];stickyban=add'>\[+\]</a><br>
[banhtml]
</body>
"}
usr << browse(html,"window=stickybans;size=700x400")
/proc/get_stickyban_from_ckey(var/ckey)
if (!ckey)
return null
ckey = ckey(ckey)
. = null
for (var/key in world.GetConfig("ban"))
if (ckey(key) == ckey)
. = stickyban2list(world.GetConfig("ban",key))
break
/proc/stickyban2list(var/ban)
if (!ban)
return null
. = params2list(ban)
if (.["keys"])
var/keys = splittext(.["keys"], ",")
var/ckeys = list()
for (var/key in keys)
var/ckey = ckey(key)
ckeys[ckey] = ckey //to make searching faster.
.["keys"] = ckeys
.["type"] = splittext(.["type"], ",")
.["IP"] = splittext(.["IP"], ",")
.["computer_id"] = splittext(.["computer_id"], ",")
/proc/list2stickyban(var/list/ban)
if (!ban || !islist(ban))
return null
. = ban.Copy()
if (.["keys"])
.["keys"] = jointext(.["keys"], ",")
if (.["type"])
.["type"] = jointext(.["type"], ",")
//internal tracking only, shouldn't be stored
. -= "existing_user_matches_this_round"
. -= "admin_matches_this_round"
. -= "matches_this_round"
. -= "reverting"
//storing these can sometimes cause sticky bans to start matching everybody
// and isn't even needed for sticky ban matching, as the hub tracks these separately
. -= "IP"
. -= "computer_id"
. = list2params(.)
/client/proc/stickybanpanel()
set name = "Sticky Ban Panel"
set category = "Admin"
if (!holder)
return
holder.stickyban_show()
File diff suppressed because it is too large Load Diff
+35
View File
@@ -0,0 +1,35 @@
/proc/getbrokeninhands()
var/text
for(var/A in typesof(/obj/item))
var/obj/item/O = new A( locate(1,1,1) )
if(!O)
continue
var/icon/IL = new(O.lefthand_file)
var/list/Lstates = IL.IconStates()
var/icon/IR = new(O.righthand_file)
var/list/Rstates = IR.IconStates()
var/icon/J = new(O.icon)
var/list/istates = J.IconStates()
if(!Lstates.Find(O.icon_state) && !Lstates.Find(O.item_state))
if(O.icon_state)
text += "[O.type] WANTS IN LEFT HAND CALLED\n\"[O.icon_state]\".\n"
if(!Rstates.Find(O.icon_state) && !Rstates.Find(O.item_state))
if(O.icon_state)
text += "[O.type] WANTS IN RIGHT HAND CALLED\n\"[O.icon_state]\".\n"
if(O.icon_state)
if(!istates.Find(O.icon_state))
text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.icon_state]\" IN \"[O.icon]\"\n"
if(O.item_state)
if(!istates.Find(O.item_state))
text += "[O.type] MISSING NORMAL ICON CALLED\n\"[O.item_state]\" IN \"[O.icon]\"\n"
text+="\n"
qdel(O)
if(text)
var/F = file("broken_icons.txt")
fdel(F)
WRITE_FILE(F, text)
to_chat(world, "Completely successfully and written to [F]")
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,634 @@
//I'm pretty sure that this is a recursive [s]descent[/s] ascent parser.
//Spec
//////////
//
// query : select_query | delete_query | update_query | call_query | explain
// explain : 'EXPLAIN' query
// select_query : 'SELECT' object_selectors
// delete_query : 'DELETE' object_selectors
// update_query : 'UPDATE' object_selectors 'SET' assignments
// call_query : 'CALL' variable 'ON' object_selectors // Note here: 'variable' does function calls. This simplifies parsing.
//
// select_item : '*' | object_type
//
// object_selectors : select_item [('FROM' | 'IN') from_item] [modifier_list]
// modifier_list : ('WHERE' bool_expression | 'MAP' expression) [modifier_list]
//
// from_item : 'world' | expression
//
// call_function : <function name> '(' [arguments] ')'
// arguments : expression [',' arguments]
//
// object_type : <type path>
//
// assignments : assignment [',' assignments]
// assignment : <variable name> '=' expression
// variable : <variable name> | <variable name> '.' variable | '[' <hex number> ']' | '[' <hex number> ']' '.' variable
//
// bool_expression : expression comparitor expression [bool_operator bool_expression]
// expression : ( unary_expression | '(' expression ')' | value ) [binary_operator expression]
// unary_expression : unary_operator ( unary_expression | value | '(' expression ')' )
// comparitor : '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
// value : variable | string | number | 'null' | object_type
// unary_operator : '!' | '-' | '~'
// binary_operator : comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%'
// bool_operator : 'AND' | '&&' | 'OR' | '||'
//
// string : ''' <some text> ''' | '"' <some text > '"'
// number : <some digits>
//
//////////
/datum/SDQL_parser
var/query_type
var/error = 0
var/list/query
var/list/tree
var/list/boolean_operators = list("and", "or", "&&", "||")
var/list/unary_operators = list("!", "-", "~")
var/list/binary_operators = list("+", "-", "/", "*", "&", "|", "^", "%")
var/list/comparitors = list("=", "==", "!=", "<>", "<", "<=", ">", ">=")
/datum/SDQL_parser/New(query_list)
query = query_list
/datum/SDQL_parser/proc/parse_error(error_message)
error = 1
to_chat(usr, "<span class='warning'>SQDL2 Parsing Error: [error_message]</span>")
return query.len + 1
/datum/SDQL_parser/proc/parse()
tree = list()
query_options(1, tree)
if(error)
return list()
else
return tree
/datum/SDQL_parser/proc/token(i)
if(i <= query.len)
return query[i]
else
return null
/datum/SDQL_parser/proc/tokens(i, num)
if(i + num <= query.len)
return query.Copy(i, i + num)
else
return null
/datum/SDQL_parser/proc/tokenl(i)
return lowertext(token(i))
/datum/SDQL_parser/proc/query_options(i, list/node)
var/list/options = list()
if(tokenl(i) == "using")
i = option_assignments(i + 1, node, options)
query(i, node)
if(length(options))
node["options"] = options
//option_assignment: query_option '=' define
/datum/SDQL_parser/proc/option_assignment(i, list/node, list/assignment_list = list())
var/type = tokenl(i)
if(!(type in SDQL2_VALID_OPTION_TYPES))
parse_error("Invalid option type: [type]")
if(!(token(i + 1) == "="))
parse_error("Invalid option assignment symbol: [token(i + 1)]")
var/val = tokenl(i + 2)
if(!(val in SDQL2_VALID_OPTION_VALUES))
parse_error("Invalid optoin value: [val]")
assignment_list[type] = val
return (i + 3)
//option_assignments: option_assignment, [',' option_assignments]
/datum/SDQL_parser/proc/option_assignments(i, list/node, list/store)
i = option_assignment(i, node, store)
if(token(i) == ",")
i = option_assignments(i + 1, node, store)
return i
//query: select_query | delete_query | update_query
/datum/SDQL_parser/proc/query(i, list/node)
query_type = tokenl(i)
switch(query_type)
if("select")
select_query(i, node)
if("delete")
delete_query(i, node)
if("update")
update_query(i, node)
if("call")
call_query(i, node)
if("explain")
node += "explain"
node["explain"] = list()
query(i + 1, node["explain"])
// select_query: 'SELECT' object_selectors
/datum/SDQL_parser/proc/select_query(i, list/node)
var/list/select = list()
i = object_selectors(i + 1, select)
node["select"] = select
return i
//delete_query: 'DELETE' object_selectors
/datum/SDQL_parser/proc/delete_query(i, list/node)
var/list/select = list()
i = object_selectors(i + 1, select)
node["delete"] = select
return i
//update_query: 'UPDATE' object_selectors 'SET' assignments
/datum/SDQL_parser/proc/update_query(i, list/node)
var/list/select = list()
i = object_selectors(i + 1, select)
node["update"] = select
if(tokenl(i) != "set")
i = parse_error("UPDATE has misplaced SET")
var/list/set_assignments = list()
i = assignments(i + 1, set_assignments)
node["set"] = set_assignments
return i
//call_query: 'CALL' call_function ['ON' object_selectors]
/datum/SDQL_parser/proc/call_query(i, list/node)
var/list/func = list()
i = variable(i + 1, func) // Yes technically does anything variable() matches but I don't care, if admins fuck up this badly then they shouldn't be allowed near SDQL.
node["call"] = func
if(tokenl(i) != "on")
return parse_error("You need to specify what to call ON.")
var/list/select = list()
i = object_selectors(i + 1, select)
node["on"] = select
return i
// object_selectors: select_item [('FROM' | 'IN') from_item] [modifier_list]
/datum/SDQL_parser/proc/object_selectors(i, list/node)
i = select_item(i, node)
if (tokenl(i) == "from" || tokenl(i) == "in")
i++
var/list/from = list()
i = from_item(i, from)
node[++node.len] = from
else
node[++node.len] = list("world")
i = modifier_list(i, node)
return i
// modifier_list: ('WHERE' bool_expression | 'MAP' expression) [modifier_list]
/datum/SDQL_parser/proc/modifier_list(i, list/node)
while (TRUE)
if (tokenl(i) == "where")
i++
node += "where"
var/list/expr = list()
i = bool_expression(i, expr)
node[++node.len] = expr
else if (tokenl(i) == "map")
i++
node += "map"
var/list/expr = list()
i = expression(i, expr)
node[++node.len] = expr
else
return i
//select_list:select_item [',' select_list]
/datum/SDQL_parser/proc/select_list(i, list/node)
i = select_item(i, node)
if(token(i) == ",")
i = select_list(i + 1, node)
return i
//assignments: assignment, [',' assignments]
/datum/SDQL_parser/proc/assignments(i, list/node)
i = assignment(i, node)
if(token(i) == ",")
i = assignments(i + 1, node)
return i
//select_item: '*' | select_function | object_type
/datum/SDQL_parser/proc/select_item(i, list/node)
if (token(i) == "*")
node += "*"
i++
else if (copytext(token(i), 1, 2) == "/")
i = object_type(i, node)
else
i = parse_error("Expected '*' or type path for select item")
return i
// Standardized method for handling the IN/FROM and WHERE options.
/datum/SDQL_parser/proc/selectors(i, list/node)
while (token(i))
var/tok = tokenl(i)
if (tok in list("from", "in"))
var/list/from = list()
i = from_item(i + 1, from)
node["from"] = from
continue
if (tok == "where")
var/list/where = list()
i = bool_expression(i + 1, where)
node["where"] = where
continue
parse_error("Expected either FROM, IN or WHERE token, found [token(i)] instead.")
return i + 1
if (!node.Find("from"))
node["from"] = list("world")
return i
//from_item: 'world' | expression
/datum/SDQL_parser/proc/from_item(i, list/node)
if(token(i) == "world")
node += "world"
i++
else
i = expression(i, node)
return i
//bool_expression: expression [bool_operator bool_expression]
/datum/SDQL_parser/proc/bool_expression(i, list/node)
var/list/bool = list()
i = expression(i, bool)
node[++node.len] = bool
if(tokenl(i) in boolean_operators)
i = bool_operator(i, node)
i = bool_expression(i, node)
return i
//assignment: <variable name> '=' expression
/datum/SDQL_parser/proc/assignment(var/i, var/list/node, var/list/assignment_list = list())
assignment_list += token(i)
if(token(i + 1) == ".")
i = assignment(i + 2, node, assignment_list)
else if(token(i + 1) == "=")
var/exp_list = list()
node[assignment_list] = exp_list
i = expression(i + 2, exp_list)
else
parse_error("Assignment expected, but no = found")
return i
//variable: <variable name> | <variable name> '.' variable | '[' <hex number> ']' | '[' <hex number> ']' '.' variable
/datum/SDQL_parser/proc/variable(i, list/node)
var/list/L = list(token(i))
node[++node.len] = L
if(token(i) == "{")
L += token(i + 1)
i += 2
if(token(i) != "}")
parse_error("Missing } at end of pointer.")
if(token(i + 1) == ".")
L += "."
i = variable(i + 2, L)
else if (token(i + 1) == "(") // OH BOY PROC
var/list/arguments = list()
i = call_function(i, null, arguments)
L += ":"
L[++L.len] = arguments
else if (token(i + 1) == "\[")
var/list/expression = list()
i = expression(i + 2, expression)
if (token(i) != "]")
parse_error("Missing ] at the end of list access.")
L += "\["
L[++L.len] = expression
i++
else
i++
return i
//object_type: <type path>
/datum/SDQL_parser/proc/object_type(i, list/node)
if (copytext(token(i), 1, 2) != "/")
return parse_error("Expected type, but it didn't begin with /")
var/path = text2path(token(i))
if (path == null)
return parse_error("Nonexistant type path: [token(i)]")
node += path
return i + 1
//comparitor: '=' | '==' | '!=' | '<>' | '<' | '<=' | '>' | '>='
/datum/SDQL_parser/proc/comparitor(i, list/node)
if(token(i) in list("=", "==", "!=", "<>", "<", "<=", ">", ">="))
node += token(i)
else
parse_error("Unknown comparitor [token(i)]")
return i + 1
//bool_operator: 'AND' | '&&' | 'OR' | '||'
/datum/SDQL_parser/proc/bool_operator(i, list/node)
if(tokenl(i) in list("and", "or", "&&", "||"))
node += token(i)
else
parse_error("Unknown comparitor [token(i)]")
return i + 1
//string: ''' <some text> ''' | '"' <some text > '"'
/datum/SDQL_parser/proc/string(i, list/node)
if(copytext(token(i), 1, 2) in list("'", "\""))
node += token(i)
else
parse_error("Expected string but found '[token(i)]'")
return i + 1
//array: '[' expression, expression, ... ']'
/datum/SDQL_parser/proc/array(var/i, var/list/node)
// Arrays get turned into this: list("[", list(exp_1a = exp_1b, ...), ...), "[" is to mark the next node as an array.
if(copytext(token(i), 1, 2) != "\[")
parse_error("Expected an array but found '[token(i)]'")
return i + 1
node += token(i) // Add the "["
var/list/expression_list = list()
i++
if(token(i) != "]")
var/list/temp_expression_list = list()
var/tok
do
tok = token(i)
if (tok == "," || tok == ":")
if (temp_expression_list == null)
parse_error("Found ',' or ':' without expression in an array.")
return i + 1
expression_list[++expression_list.len] = temp_expression_list
temp_expression_list = null
if (tok == ":")
temp_expression_list = list()
i = expression(i + 1, temp_expression_list)
expression_list[expression_list[expression_list.len]] = temp_expression_list
temp_expression_list = null
tok = token(i)
if (tok != ",")
if (tok == "]")
break
parse_error("Expected ',' or ']' after array assoc value, but found '[token(i)]'")
return i
i++
continue
temp_expression_list = list()
i = expression(i, temp_expression_list)
// Ok, what the fuck BYOND?
// Not having these lines here causes the parser to die
// on an error saying that list/token() doesn't exist as a proc.
// These lines prevent that.
// I assume the compiler/VM is shitting itself and swapping out some variables internally?
// While throwing in debug logging it disappeared
// And these 3 lines prevent it from happening while being quiet.
// So.. it works.
// Don't touch it.
var/whatthefuck = i
whatthefuck = src.type
whatthefuck = whatthefuck
while(token(i) && token(i) != "]")
if (temp_expression_list)
expression_list[++expression_list.len] = temp_expression_list
node[++node.len] = expression_list
return i + 1
//call_function: <function name> ['(' [arguments] ')']
/datum/SDQL_parser/proc/call_function(i, list/node, list/arguments)
if(length(tokenl(i)))
var/procname = ""
if(tokenl(i) == "global" && token(i + 1) == ".") // Global proc.
i += 2
procname = "global."
node += procname + token(i++)
if(token(i) != "(")
parse_error("Expected ( but found '[token(i)]'")
else if(token(i + 1) != ")")
var/list/temp_expression_list = list()
do
i = expression(i + 1, temp_expression_list)
if(token(i) == ",")
arguments[++arguments.len] = temp_expression_list
temp_expression_list = list()
continue
while(token(i) && token(i) != ")")
arguments[++arguments.len] = temp_expression_list // The code this is copy pasted from won't be executed when it's the last param, this fixes that.
else
i++
else
parse_error("Expected a function but found nothing")
return i + 1
//expression: ( unary_expression | '(' expression ')' | value ) [binary_operator expression]
/datum/SDQL_parser/proc/expression(i, list/node)
if(token(i) in unary_operators)
i = unary_expression(i, node)
else if(token(i) == "(")
var/list/expr = list()
i = expression(i + 1, expr)
if(token(i) != ")")
parse_error("Missing ) at end of expression.")
else
i++
node[++node.len] = expr
else
i = value(i, node)
if(token(i) in binary_operators)
i = binary_operator(i, node)
i = expression(i, node)
else if(token(i) in comparitors)
i = binary_operator(i, node)
var/list/rhs = list()
i = expression(i, rhs)
node[++node.len] = rhs
return i
//unary_expression: unary_operator ( unary_expression | value | '(' expression ')' )
/datum/SDQL_parser/proc/unary_expression(i, list/node)
if(token(i) in unary_operators)
var/list/unary_exp = list()
unary_exp += token(i)
i++
if(token(i) in unary_operators)
i = unary_expression(i, unary_exp)
else if(token(i) == "(")
var/list/expr = list()
i = expression(i + 1, expr)
if(token(i) != ")")
parse_error("Missing ) at end of expression.")
else
i++
unary_exp[++unary_exp.len] = expr
else
i = value(i, unary_exp)
node[++node.len] = unary_exp
else
parse_error("Expected unary operator but found '[token(i)]'")
return i
//binary_operator: comparitor | '+' | '-' | '/' | '*' | '&' | '|' | '^' | '%'
/datum/SDQL_parser/proc/binary_operator(i, list/node)
if(token(i) in (binary_operators + comparitors))
node += token(i)
else
parse_error("Unknown binary operator [token(i)]")
return i + 1
//value: variable | string | number | 'null' | object_type
/datum/SDQL_parser/proc/value(i, list/node)
if(token(i) == "null")
node += "null"
i++
else if(lowertext(copytext(token(i), 1, 3)) == "0x" && isnum(hex2num(copytext(token(i), 3))))
node += hex2num(copytext(token(i), 3))
i++
else if(isnum(text2num(token(i))))
node += text2num(token(i))
i++
else if(copytext(token(i), 1, 2) in list("'", "\""))
i = string(i, node)
else if(copytext(token(i), 1, 2) == "\[") // Start a list.
i = array(i, node)
else if(copytext(token(i), 1, 2) == "/")
i = object_type(i, node)
else
i = variable(i, node)
return i
@@ -0,0 +1,223 @@
// Wrappers for BYOND default procs which can't directly be called by call().
/proc/_abs(A)
return abs(A)
/proc/_animate(atom/A, set_vars, time = 10, loop = 1, easing = LINEAR_EASING, flags = null)
var/mutable_appearance/MA = new()
for(var/v in set_vars)
MA.vars[v] = set_vars[v]
animate(A, appearance = MA, time, loop, easing, flags)
/proc/_acrccos(A)
return arccos(A)
/proc/_arcsin(A)
return arcsin(A)
/proc/_ascii2text(A)
return ascii2text(A)
/proc/_block(Start, End)
return block(Start, End)
/proc/_ckey(Key)
return ckey(Key)
/proc/_ckeyEx(Key)
return ckeyEx(Key)
/proc/_copytext(T, Start = 1, End = 0)
return copytext(T, Start, End)
/proc/_cos(X)
return cos(X)
/proc/_get_dir(Loc1, Loc2)
return get_dir(Loc1, Loc2)
/proc/_get_dist(Loc1, Loc2)
return get_dist(Loc1, Loc2)
/proc/_get_step(Ref, Dir)
return get_step(Ref, Dir)
/proc/_hearers(Depth = world.view, Center = usr)
return hearers(Depth, Center)
/proc/_image(icon, loc, icon_state, layer, dir)
return image(icon, loc, icon_state, layer, dir)
/proc/_istype(object, type)
return istype(object, type)
/proc/_ispath(path, type)
return ispath(path, type)
/proc/_length(E)
return length(E)
/proc/_link(thing, url)
thing << link(url)
/proc/_locate(X, Y, Z)
if (isnull(Y)) // Assuming that it's only a single-argument call.
return locate(X)
return locate(X, Y, Z)
/proc/_log(X, Y)
return log(X, Y)
/proc/_lowertext(T)
return lowertext(T)
/proc/_matrix(a, b, c, d, e, f)
return matrix(a, b, c, d, e, f)
/proc/_max(...)
return max(arglist(args))
/proc/_md5(T)
return md5(T)
/proc/_min(...)
return min(arglist(args))
/proc/_new(type, arguments)
return new type (arglist(arguments))
/proc/_num2text(N, SigFig = 6)
return num2text(N, SigFig)
/proc/_ohearers(Dist, Center = usr)
return ohearers(Dist, Center)
/proc/_orange(Dist, Center = usr)
return orange(Dist, Center)
/proc/_output(thing, msg, control)
thing << output(msg, control)
/proc/_oview(Dist, Center = usr)
return oview(Dist, Center)
/proc/_oviewers(Dist, Center = usr)
return oviewers(Dist, Center)
/proc/_params2list(Params)
return params2list(Params)
/proc/_pick(...)
return pick(arglist(args))
/proc/_prob(P)
return prob(P)
/proc/_rand(L = 0, H = 1)
return rand(L, H)
/proc/_range(Dist, Center = usr)
return range(Dist, Center)
/proc/_regex(pattern, flags)
return regex(pattern, flags)
/proc/_REGEX_QUOTE(text)
return REGEX_QUOTE(text)
/proc/_REGEX_QUOTE_REPLACEMENT(text)
return REGEX_QUOTE_REPLACEMENT(text)
/proc/_replacetext(Haystack, Needle, Replacement, Start = 1,End = 0)
return replacetext(Haystack, Needle, Replacement, Start, End)
/proc/_replacetextEx(Haystack, Needle, Replacement, Start = 1,End = 0)
return replacetextEx(Haystack, Needle, Replacement, Start, End)
/proc/_rgb(R, G, B)
return rgb(R, G, B)
/proc/_rgba(R, G, B, A)
return rgb(R, G, B, A)
/proc/_roll(dice)
return roll(dice)
/proc/_round(A, B = 1)
return round(A, B)
/proc/_sin(X)
return sin(X)
/proc/_list_add(list/L, ...)
if (args.len < 2)
return
L += args.Copy(2)
/proc/_list_copy(list/L, Start = 1, End = 0)
return L.Copy(Start, End)
/proc/_list_cut(list/L, Start = 1, End = 0)
L.Cut(Start, End)
/proc/_list_find(list/L, Elem, Start = 1, End = 0)
return L.Find(Elem, Start, End)
/proc/_list_insert(list/L, Index, Item)
return L.Insert(Index, Item)
/proc/_list_join(list/L, Glue, Start = 0, End = 1)
return L.Join(Glue, Start, End)
/proc/_list_remove(list/L, ...)
if (args.len < 2)
return
L -= args.Copy(2)
/proc/_list_set(list/L, key, value)
L[key] = value
/proc/_list_numerical_add(L, key, num)
L[key] += num
/proc/_list_swap(list/L, Index1, Index2)
L.Swap(Index1, Index2)
/proc/_list_get(list/L, index)
return L[index]
/proc/_walk(ref, dir, lag)
walk(ref, dir, lag)
/proc/_walk_towards(ref, trg, lag)
walk_towards(ref, trg, lag)
/proc/_walk_to(ref, trg, min, lag)
walk_to(ref, trg, min, lag)
/proc/_walk_away(ref, trg, max, lag)
walk_away(ref, trg, max, lag)
/proc/_walk_rand(ref, lag)
walk_rand(ref, lag)
/proc/_step(ref, dir)
step(ref, dir)
/proc/_step_rand(ref)
step_rand(ref)
/proc/_step_to(ref, trg, min)
step_to(ref, trg, min)
/proc/_step_towards(ref, trg)
step_towards(ref, trg)
/proc/_step_away(ref, trg, max)
step_away(ref, trg, max)
/proc/_instantiate_at(thing, location)
new thing(location)
+698
View File
@@ -0,0 +1,698 @@
/client/var/adminhelptimerid = 0 //a timer id for returning the ahelp verb
/client/var/datum/admin_help/current_ticket //the current ticket the (usually) not-admin client is dealing with
//
//TICKET MANAGER
//
GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/datum/admin_help_tickets
var/list/active_tickets = list()
var/list/closed_tickets = list()
var/list/resolved_tickets = list()
var/obj/effect/statclick/ticket_list/astatclick = new(null, null, AHELP_ACTIVE)
var/obj/effect/statclick/ticket_list/cstatclick = new(null, null, AHELP_CLOSED)
var/obj/effect/statclick/ticket_list/rstatclick = new(null, null, AHELP_RESOLVED)
/datum/admin_help_tickets/Destroy()
QDEL_LIST(active_tickets)
QDEL_LIST(closed_tickets)
QDEL_LIST(resolved_tickets)
QDEL_NULL(astatclick)
QDEL_NULL(cstatclick)
QDEL_NULL(rstatclick)
return ..()
/datum/admin_help_tickets/proc/TicketByID(id)
var/list/lists = list(active_tickets, closed_tickets, resolved_tickets)
for(var/I in lists)
for(var/J in I)
var/datum/admin_help/AH = J
if(AH.id == id)
return J
/datum/admin_help_tickets/proc/TicketsByCKey(ckey)
. = list()
var/list/lists = list(active_tickets, closed_tickets, resolved_tickets)
for(var/I in lists)
for(var/J in I)
var/datum/admin_help/AH = J
if(AH.initiator_ckey == ckey)
. += AH
//private
/datum/admin_help_tickets/proc/ListInsert(datum/admin_help/new_ticket)
var/list/ticket_list
switch(new_ticket.state)
if(AHELP_ACTIVE)
ticket_list = active_tickets
if(AHELP_CLOSED)
ticket_list = closed_tickets
if(AHELP_RESOLVED)
ticket_list = resolved_tickets
else
CRASH("Invalid ticket state: [new_ticket.state]")
var/num_closed = ticket_list.len
if(num_closed)
for(var/I in 1 to num_closed)
var/datum/admin_help/AH = ticket_list[I]
if(AH.id > new_ticket.id)
ticket_list.Insert(I, new_ticket)
return
ticket_list += new_ticket
//opens the ticket listings for one of the 3 states
/datum/admin_help_tickets/proc/BrowseTickets(state)
var/list/l2b
var/title
switch(state)
if(AHELP_ACTIVE)
l2b = active_tickets
title = "Active Tickets"
if(AHELP_CLOSED)
l2b = closed_tickets
title = "Closed Tickets"
if(AHELP_RESOLVED)
l2b = resolved_tickets
title = "Resolved Tickets"
if(!l2b)
return
var/list/dat = list("<html><head><title>[title]</title></head>")
dat += "<A href='?_src_=holder;[HrefToken()];ahelp_tickets=[state]'>Refresh</A><br><br>"
for(var/I in l2b)
var/datum/admin_help/AH = I
dat += "<span class='adminnotice'><span class='adminhelp'>Ticket #[AH.id]</span>: <A href='?_src_=holder;[HrefToken()];ahelp=[REF(AH)];ahelp_action=ticket'>[AH.initiator_key_name]: [AH.name]</A></span><br>"
usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480")
//Tickets statpanel
/datum/admin_help_tickets/proc/stat_entry()
var/num_disconnected = 0
stat("Active Tickets:", astatclick.update("[active_tickets.len]"))
for(var/I in active_tickets)
var/datum/admin_help/AH = I
if(AH.initiator)
stat("#[AH.id]. [AH.initiator_key_name]:", AH.statclick.update())
else
++num_disconnected
if(num_disconnected)
stat("Disconnected:", astatclick.update("[num_disconnected]"))
stat("Closed Tickets:", cstatclick.update("[closed_tickets.len]"))
stat("Resolved Tickets:", rstatclick.update("[resolved_tickets.len]"))
//Reassociate still open ticket if one exists
/datum/admin_help_tickets/proc/ClientLogin(client/C)
C.current_ticket = CKey2ActiveTicket(C.ckey)
if(C.current_ticket)
C.current_ticket.initiator = C
C.current_ticket.AddInteraction("Client reconnected.")
//Dissasociate ticket
/datum/admin_help_tickets/proc/ClientLogout(client/C)
if(C.current_ticket)
C.current_ticket.AddInteraction("Client disconnected.")
C.current_ticket.initiator = null
C.current_ticket = null
//Get a ticket given a ckey
/datum/admin_help_tickets/proc/CKey2ActiveTicket(ckey)
for(var/I in active_tickets)
var/datum/admin_help/AH = I
if(AH.initiator_ckey == ckey)
return AH
//
//TICKET LIST STATCLICK
//
/obj/effect/statclick/ticket_list
var/current_state
/obj/effect/statclick/ticket_list/New(loc, name, state)
current_state = state
..()
/obj/effect/statclick/ticket_list/Click()
GLOB.ahelp_tickets.BrowseTickets(current_state)
//
//TICKET DATUM
//
/datum/admin_help
var/id
var/name
var/state = AHELP_ACTIVE
var/opened_at
var/closed_at
var/client/initiator //semi-misnomer, it's the person who ahelped/was bwoinked
var/initiator_ckey
var/initiator_key_name
var/heard_by_no_admins = FALSE
var/list/_interactions //use AddInteraction() or, preferably, admin_ticket_log()
var/obj/effect/statclick/ahelp/statclick
var/static/ticket_counter = 0
//call this on its own to create a ticket, don't manually assign current_ticket
//msg is the title of the ticket: usually the ahelp text
//is_bwoink is TRUE if this ticket was started by an admin PM
/datum/admin_help/New(msg, client/C, is_bwoink)
//clean the input msg
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
if(!msg || !C || !C.mob)
qdel(src)
return
id = ++ticket_counter
opened_at = world.time
name = msg
initiator = C
initiator_ckey = initiator.ckey
initiator_key_name = key_name(initiator, FALSE, TRUE)
if(initiator.current_ticket) //This is a bug
stack_trace("Multiple ahelp current_tickets")
initiator.current_ticket.AddInteraction("Ticket erroneously left open by code")
initiator.current_ticket.Close()
initiator.current_ticket = src
TimeoutVerb()
statclick = new(null, src)
_interactions = list()
if(is_bwoink)
AddInteraction("<font color='blue'>[key_name_admin(usr)] PM'd [LinkedReplyName()]</font>")
message_admins("<font color='blue'>Ticket [TicketHref("#[id]")] created</font>")
else
MessageNoRecipient(msg)
//send it to irc if nobody is on and tell us how many were on
var/admin_number_present = send2irc_adminless_only(initiator_ckey, "Ticket #[id]: [name]")
log_admin_private("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.")
if(admin_number_present <= 0)
to_chat(C, "<span class='notice'>No active admins are online, your adminhelp was sent to the admin irc.</span>")
heard_by_no_admins = TRUE
GLOB.ahelp_tickets.active_tickets += src
/datum/admin_help/Destroy()
RemoveActive()
GLOB.ahelp_tickets.closed_tickets -= src
GLOB.ahelp_tickets.resolved_tickets -= src
return ..()
/datum/admin_help/proc/AddInteraction(formatted_message)
if(heard_by_no_admins && usr && usr.ckey != initiator_ckey)
heard_by_no_admins = FALSE
send2irc(initiator_ckey, "Ticket #[id]: Answered by [key_name(usr)]")
_interactions += "[TIME_STAMP("hh:mm:ss", FALSE)]: [formatted_message]"
//Removes the ahelp verb and returns it after 2 minutes
/datum/admin_help/proc/TimeoutVerb()
initiator.verbs -= /client/verb/adminhelp
initiator.adminhelptimerid = addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 1200, TIMER_STOPPABLE) //2 minute cooldown of admin helps
//private
/datum/admin_help/proc/FullMonty(ref_src)
if(!ref_src)
ref_src = "[REF(src)]"
. = ADMIN_FULLMONTY_NONAME(initiator.mob)
if(state == AHELP_ACTIVE)
. += ClosureLinks(ref_src)
//private
/datum/admin_help/proc/ClosureLinks(ref_src)
if(!ref_src)
ref_src = "[REF(src)]"
. = " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=reject'>REJT</A>)"
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=icissue'>IC</A>)"
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=close'>CLOSE</A>)"
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=resolve'>RSLVE</A>)"
. += " (<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=handleissue'>HANDLE</A>)"
//private
/datum/admin_help/proc/LinkedReplyName(ref_src)
if(!ref_src)
ref_src = "[REF(src)]"
return "<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=reply'>[initiator_key_name]</A>"
//private
/datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket")
if(!ref_src)
ref_src = "[REF(src)]"
return "<A HREF='?_src_=holder;[HrefToken(TRUE)];ahelp=[ref_src];ahelp_action=[action]'>[msg]</A>"
//message from the initiator without a target, all admins will see this
//won't bug irc
/datum/admin_help/proc/MessageNoRecipient(msg)
var/ref_src = "[REF(src)]"
//Message to be sent to all admins
var/admin_msg = "<span class='adminnotice'><span class='adminhelp'>Ticket [TicketHref("#[id]", ref_src)]</span><b>: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]:</b> <span class='linkify'>[keywords_lookup(msg)]</span></span>"
AddInteraction("<font color='red'>[LinkedReplyName(ref_src)]: [msg]</font>")
//send this msg to all admins
for(var/client/X in GLOB.admins)
if(X.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(X, sound('sound/effects/adminhelp.ogg'))
window_flash(X, ignorepref = TRUE)
to_chat(X, admin_msg)
//show it to the person adminhelping too
to_chat(initiator, "<span class='adminnotice'>PM to-<b>Admins</b>: <span class='linkify'>[msg]</span></span>")
//Reopen a closed ticket
/datum/admin_help/proc/Reopen()
if(state == AHELP_ACTIVE)
to_chat(usr, "<span class='warning'>This ticket is already open.</span>")
return
if(GLOB.ahelp_tickets.CKey2ActiveTicket(initiator_ckey))
to_chat(usr, "<span class='warning'>This user already has an active ticket, cannot reopen this one.</span>")
return
statclick = new(null, src)
GLOB.ahelp_tickets.active_tickets += src
GLOB.ahelp_tickets.closed_tickets -= src
GLOB.ahelp_tickets.resolved_tickets -= src
switch(state)
if(AHELP_CLOSED)
SSblackbox.record_feedback("tally", "ahelp_stats", -1, "closed")
if(AHELP_RESOLVED)
SSblackbox.record_feedback("tally", "ahelp_stats", -1, "resolved")
state = AHELP_ACTIVE
closed_at = null
if(initiator)
initiator.current_ticket = src
AddInteraction("<font color='purple'>Reopened by [key_name_admin(usr)]</font>")
var/msg = "<span class='adminhelp'>Ticket [TicketHref("#[id]")] reopened by [key_name_admin(usr)].</span>"
message_admins(msg)
log_admin_private(msg)
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "reopened")
TicketPanel() //can only be done from here, so refresh it
//private
/datum/admin_help/proc/RemoveActive()
if(state != AHELP_ACTIVE)
return
closed_at = world.time
QDEL_NULL(statclick)
GLOB.ahelp_tickets.active_tickets -= src
if(initiator && initiator.current_ticket == src)
initiator.current_ticket = null
//Mark open ticket as closed/meme
/datum/admin_help/proc/Close(key_name = key_name_admin(usr), silent = FALSE)
if(state != AHELP_ACTIVE)
return
RemoveActive()
state = AHELP_CLOSED
GLOB.ahelp_tickets.ListInsert(src)
AddInteraction("<font color='red'>Closed by [key_name].</font>")
if(!silent)
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "closed")
var/msg = "Ticket [TicketHref("#[id]")] closed by [key_name]."
message_admins(msg)
log_admin_private(msg)
//Mark open ticket as resolved/legitimate, returns ahelp verb
/datum/admin_help/proc/Resolve(key_name = key_name_admin(usr), silent = FALSE)
if(state != AHELP_ACTIVE)
return
RemoveActive()
state = AHELP_RESOLVED
GLOB.ahelp_tickets.ListInsert(src)
addtimer(CALLBACK(initiator, /client/proc/giveadminhelpverb), 50)
AddInteraction("<font color='green'>Resolved by [key_name].</font>")
to_chat(initiator, "<span class='adminhelp'>Your ticket has been resolved by an admin. The Adminhelp verb will be returned to you shortly.</span>")
if(!silent)
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "resolved")
var/msg = "Ticket [TicketHref("#[id]")] resolved by [key_name]"
message_admins(msg)
log_admin_private(msg)
//Close and return ahelp verb, use if ticket is incoherent
/datum/admin_help/proc/Reject(key_name = key_name_admin(usr))
if(state != AHELP_ACTIVE)
return
if(initiator)
initiator.giveadminhelpverb()
SEND_SOUND(initiator, sound('sound/effects/adminhelp.ogg'))
to_chat(initiator, "<font color='red' size='4'><b>- AdminHelp Rejected! -</b></font>")
to_chat(initiator, "<font color='red'><b>Your admin help was rejected.</b> The adminhelp verb has been returned to you so that you may try again.</font>")
to_chat(initiator, "Please try to be calm, clear, and descriptive in admin helps, do not assume the admin has seen any related events, and clearly state the names of anybody you are reporting.")
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "rejected")
var/msg = "Ticket [TicketHref("#[id]")] rejected by [key_name]"
message_admins(msg)
log_admin_private(msg)
AddInteraction("Rejected by [key_name].")
Close(silent = TRUE)
//Resolve ticket with IC Issue message
/datum/admin_help/proc/ICIssue(key_name = key_name_admin(usr))
if(state != AHELP_ACTIVE)
return
var/msg = "<font color='red' size='4'><b>- AdminHelp marked as IC issue! -</b></font><br>"
msg += "<font color='red'><b>Losing is part of the game!</b></font><br>"
msg += "<font color='red'>It is also possible that your ahelp is unable to be answered properly, due to events occurring in the round.</font>"
if(initiator)
to_chat(initiator, msg)
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "IC")
msg = "Ticket [TicketHref("#[id]")] marked as IC by [key_name]"
message_admins(msg)
log_admin_private(msg)
AddInteraction("Marked as IC issue by [key_name]")
Resolve(silent = TRUE)
//Let the initiator know their ahelp is being handled
/datum/admin_help/proc/HandleIssue(key_name = key_name_admin(usr))
if(state != AHELP_ACTIVE)
return
var/msg = "<span class ='adminhelp'>Your ticket is now being handled by an admin. Please be patient.</span>"
if(initiator)
to_chat(initiator, msg)
SSblackbox.record_feedback("tally", "ahelp_stats", 1, "handling")
msg = "Ticket [TicketHref("#[id]")] is being handled by [key_name]"
message_admins(msg)
log_admin_private(msg)
AddInteraction("Being handled by [key_name]")
//Show the ticket panel
/datum/admin_help/proc/TicketPanel()
var/list/dat = list("<html><head><title>Ticket #[id]</title></head>")
var/ref_src = "[REF(src)]"
dat += "<h4>Admin Help Ticket #[id]: [LinkedReplyName(ref_src)]</h4>"
dat += "<b>State: "
switch(state)
if(AHELP_ACTIVE)
dat += "<font color='red'>OPEN</font>"
if(AHELP_RESOLVED)
dat += "<font color='green'>RESOLVED</font>"
if(AHELP_CLOSED)
dat += "CLOSED"
else
dat += "UNKNOWN"
dat += "</b>[GLOB.TAB][TicketHref("Refresh", ref_src)][GLOB.TAB][TicketHref("Re-Title", ref_src, "retitle")]"
if(state != AHELP_ACTIVE)
dat += "[GLOB.TAB][TicketHref("Reopen", ref_src, "reopen")]"
dat += "<br><br>Opened at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)"
if(closed_at)
dat += "<br>Closed at: [GAMETIMESTAMP("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)"
dat += "<br><br>"
if(initiator)
dat += "<b>Actions:</b> [FullMonty(ref_src)]<br>"
else
dat += "<b>DISCONNECTED</b>[GLOB.TAB][ClosureLinks(ref_src)]<br>"
dat += "<br><b>Log:</b><br><br>"
for(var/I in _interactions)
dat += "[I]<br>"
usr << browse(dat.Join(), "window=ahelp[id];size=620x480")
/datum/admin_help/proc/Retitle()
var/new_title = input(usr, "Enter a title for the ticket", "Rename Ticket", name) as text|null
if(new_title)
name = new_title
//not saying the original name cause it could be a long ass message
var/msg = "Ticket [TicketHref("#[id]")] titled [name] by [key_name_admin(usr)]"
message_admins(msg)
log_admin_private(msg)
TicketPanel() //we have to be here to do this
//Forwarded action from admin/Topic
/datum/admin_help/proc/Action(action)
testing("Ahelp action: [action]")
switch(action)
if("ticket")
TicketPanel()
if("retitle")
Retitle()
if("reject")
Reject()
if("reply")
usr.client.cmd_ahelp_reply(initiator)
if("icissue")
ICIssue()
if("close")
Close()
if("resolve")
Resolve()
if("handleissue")
HandleIssue()
if("reopen")
Reopen()
//
// TICKET STATCLICK
//
/obj/effect/statclick/ahelp
var/datum/admin_help/ahelp_datum
/obj/effect/statclick/ahelp/Initialize(mapload, datum/admin_help/AH)
ahelp_datum = AH
. = ..()
/obj/effect/statclick/ahelp/update()
return ..(ahelp_datum.name)
/obj/effect/statclick/ahelp/Click()
ahelp_datum.TicketPanel()
/obj/effect/statclick/ahelp/Destroy()
ahelp_datum = null
return ..()
//
// CLIENT PROCS
//
/client/proc/giveadminhelpverb()
src.verbs |= /client/verb/adminhelp
deltimer(adminhelptimerid)
adminhelptimerid = 0
// Used for methods where input via arg doesn't work
/client/proc/get_adminhelp()
var/msg = input(src, "Please describe your problem concisely and an admin will help as soon as they're able.", "Adminhelp contents") as text
adminhelp(msg)
/client/verb/adminhelp(msg as text)
set category = "Admin"
set name = "Adminhelp"
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
return
//handle muting and automuting
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You cannot send adminhelps (Muted).</span>")
return
if(handle_spam_prevention(msg,MUTE_ADMINHELP))
return
msg = trim(msg)
if(!msg)
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Adminhelp") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
if(current_ticket)
if(alert(usr, "You already have a ticket open. Is this for the same issue?",,"Yes","No") != "No")
if(current_ticket)
current_ticket.MessageNoRecipient(msg)
current_ticket.TimeoutVerb()
return
else
to_chat(usr, "<span class='warning'>Ticket not found, creating new one...</span>")
else
current_ticket.AddInteraction("[key_name_admin(usr)] opened a new ticket.")
current_ticket.Close()
new /datum/admin_help(msg, src, FALSE)
//
// LOGGING
//
//Use this proc when an admin takes action that may be related to an open ticket on what
//what can be a client, ckey, or mob
/proc/admin_ticket_log(what, message)
var/client/C
var/mob/Mob = what
if(istype(Mob))
C = Mob.client
else
C = what
if(istype(C) && C.current_ticket)
C.current_ticket.AddInteraction(message)
return C.current_ticket
if(istext(what)) //ckey
var/datum/admin_help/AH = GLOB.ahelp_tickets.CKey2ActiveTicket(what)
if(AH)
AH.AddInteraction(message)
return AH
//
// HELPER PROCS
//
/proc/get_admin_counts(requiredflags = R_BAN)
. = list("total" = list(), "noflags" = list(), "afk" = list(), "stealth" = list(), "present" = list())
for(var/client/X in GLOB.admins)
.["total"] += X
if(requiredflags != 0 && !check_rights_for(X, requiredflags))
.["noflags"] += X
else if(X.is_afk())
.["afk"] += X
else if(X.holder.fakekey)
.["stealth"] += X
else
.["present"] += X
/proc/send2irc_adminless_only(source, msg, requiredflags = R_BAN)
var/list/adm = get_admin_counts(requiredflags)
var/list/activemins = adm["present"]
. = activemins.len
if(. <= 0)
var/final = ""
var/list/afkmins = adm["afk"]
var/list/stealthmins = adm["stealth"]
var/list/powerlessmins = adm["noflags"]
var/list/allmins = adm["total"]
if(!afkmins.len && !stealthmins.len && !powerlessmins.len)
final = "[msg] - No admins online"
else
final = "[msg] - All admins stealthed\[[english_list(stealthmins)]\], AFK\[[english_list(afkmins)]\], or lacks +BAN\[[english_list(powerlessmins)]\]! Total: [allmins.len] "
send2irc(source,final)
send2otherserver(source,final)
/proc/send2irc(msg,msg2)
msg = replacetext(replacetext(msg, "\proper", ""), "\improper", "")
msg2 = replacetext(replacetext(msg2, "\proper", ""), "\improper", "")
world.TgsTargetedChatBroadcast("[msg] | [msg2]", TRUE)
/proc/send2otherserver(source,msg,type = "Ahelp")
var/comms_key = CONFIG_GET(string/comms_key)
if(!comms_key)
return
var/list/message = list()
message["message_sender"] = source
message["message"] = msg
message["source"] = "([CONFIG_GET(string/cross_comms_name)])"
message["key"] = comms_key
message += type
var/list/servers = CONFIG_GET(keyed_list/cross_server)
for(var/I in servers)
world.Export("[servers[I]]?[list2params(message)]")
/proc/ircadminwho()
var/list/message = list("Admins: ")
var/list/admin_keys = list()
for(var/adm in GLOB.admins)
var/client/C = adm
admin_keys += "[C][C.holder.fakekey ? "(Stealth)" : ""][C.is_afk() ? "(AFK)" : ""]"
for(var/admin in admin_keys)
if(LAZYLEN(message) > 1)
message += ", [admin]"
else
message += "[admin]"
return jointext(message, "")
/proc/keywords_lookup(msg,irc)
//This is a list of words which are ignored by the parser when comparing message contents for names. MUST BE IN LOWER CASE!
var/list/adminhelp_ignored_words = list("unknown","the","a","an","of","monkey","alien","as", "i")
//explode the input msg into a list
var/list/msglist = splittext(msg, " ")
//generate keywords lookup
var/list/surnames = list()
var/list/forenames = list()
var/list/ckeys = list()
var/founds = ""
for(var/mob/M in GLOB.mob_list)
var/list/indexing = list(M.real_name, M.name)
if(M.mind)
indexing += M.mind.name
for(var/string in indexing)
var/list/L = splittext(string, " ")
var/surname_found = 0
//surnames
for(var/i=L.len, i>=1, i--)
var/word = ckey(L[i])
if(word)
surnames[word] = M
surname_found = i
break
//forenames
for(var/i=1, i<surname_found, i++)
var/word = ckey(L[i])
if(word)
forenames[word] = M
//ckeys
ckeys[M.ckey] = M
var/ai_found = 0
msg = ""
var/list/mobs_found = list()
for(var/original_word in msglist)
var/word = ckey(original_word)
if(word)
if(!(word in adminhelp_ignored_words))
if(word == "ai")
ai_found = 1
else
var/mob/found = ckeys[word]
if(!found)
found = surnames[word]
if(!found)
found = forenames[word]
if(found)
if(!(found in mobs_found))
mobs_found += found
if(!ai_found && isAI(found))
ai_found = 1
var/is_antag = 0
if(found.mind && found.mind.special_role)
is_antag = 1
founds += "Name: [found.name]([found.real_name]) Key: [found.key] Ckey: [found.ckey] [is_antag ? "(Antag)" : null] "
msg += "[original_word]<font size='1' color='[is_antag ? "red" : "black"]'>(<A HREF='?_src_=holder;[HrefToken(TRUE)];adminmoreinfo=[REF(found)]'>?</A>|<A HREF='?_src_=holder;[HrefToken(TRUE)];adminplayerobservefollow=[REF(found)]'>F</A>)</font> "
continue
msg += "[original_word] "
if(irc)
if(founds == "")
return "Search Failed"
else
return founds
return msg
+157
View File
@@ -0,0 +1,157 @@
/client/proc/jumptoarea(area/A in GLOB.sortedAreas)
set name = "Jump to Area"
set desc = "Area to jump to"
set category = "Admin"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
if(!A)
return
var/list/turfs = list()
for(var/turf/T in A)
if(T.density)
continue
turfs.Add(T)
var/turf/T = safepick(turfs)
if(!T)
to_chat(src, "Nowhere to jump to!")
return
usr.forceMove(T)
log_admin("[key_name(usr)] jumped to [AREACOORD(A)]")
message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/jumptoturf(turf/T in world)
set name = "Jump to Turf"
set category = "Admin"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
log_admin("[key_name(usr)] jumped to [AREACOORD(T)]")
message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]")
usr.forceMove(T)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/proc/jumptomob(mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Jump to Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
log_admin("[key_name(usr)] jumped to [key_name(M)]")
message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]")
if(src.mob)
var/mob/A = src.mob
var/turf/T = get_turf(M)
if(T && isturf(T))
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
A.forceMove(M.loc)
else
to_chat(A, "This mob is not located in the game world.")
/client/proc/jumptocoord(tx as num, ty as num, tz as num)
set category = "Admin"
set name = "Jump to Coordinate"
if (!holder)
to_chat(src, "Only administrators may use this command.")
return
if(src.mob)
var/mob/A = src.mob
var/turf/T = locate(tx,ty,tz)
A.forceMove(T)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]")
/client/proc/jumptokey()
set category = "Admin"
set name = "Jump to Key"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/list/keys = list()
for(var/mob/M in GLOB.player_list)
keys += M.client
var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
if(!selection)
to_chat(src, "No keys found.")
return
var/mob/M = selection.mob
log_admin("[key_name(usr)] jumped to [key_name(M)]")
message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]")
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list)
set category = "Admin"
set name = "Get Mob"
set desc = "Mob to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/atom/loc = get_turf(usr)
log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]"
message_admins(msg)
admin_ticket_log(M, msg)
M.forceMove(loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/Getkey()
set category = "Admin"
set name = "Get Key"
set desc = "Key to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/list/keys = list()
for(var/mob/M in GLOB.player_list)
keys += M.client
var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
if(!selection)
return
var/mob/M = selection.mob
if(!M)
return
log_admin("[key_name(usr)] teleported [key_name(M)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]"
message_admins(msg)
admin_ticket_log(M, msg)
if(M)
M.forceMove(get_turf(usr))
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/sendmob(mob/M in sortmobs())
set category = "Admin"
set name = "Send Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null
if(A && istype(A))
if(M.forceMove(safepick(get_area_turfs(A))))
log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]"
message_admins(msg)
admin_ticket_log(M, msg)
else
to_chat(src, "Failed to move mob to a valid location.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+325
View File
@@ -0,0 +1,325 @@
#define IRCREPLYCOUNT 2
//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm
/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list)
set category = null
set name = "Admin PM Mob"
if(!holder)
to_chat(src, "<span class='danger'>Error: Admin-PM-Context: Only administrators may use this command.</span>")
return
if( !ismob(M) || !M.client )
return
cmd_admin_pm(M.client,null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm
/client/proc/cmd_admin_pm_panel()
set category = "Admin"
set name = "Admin PM"
if(!holder)
to_chat(src, "<span class='danger'>Error: Admin-PM-Panel: Only administrators may use this command.</span>")
return
var/list/client/targets[0]
for(var/client/T)
if(T.mob)
if(isnewplayer(T.mob))
targets["(New Player) - [T]"] = T
else if(isobserver(T.mob))
targets["[T.mob.name](Ghost) - [T]"] = T
else
targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T
else
targets["(No Mob) - [T]"] = T
var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets)
cmd_admin_pm(targets[target],null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_ahelp_reply(whom)
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
return
var/client/C
if(istext(whom))
if(cmptext(copytext(whom,1,2),"@"))
whom = findStealthKey(whom)
C = GLOB.directory[whom]
else if(istype(whom, /client))
C = whom
if(!C)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
return
var/datum/admin_help/AH = C.current_ticket
if(AH)
message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.")
var/msg = input(src,"Message:", "Private message to [key_name(C, 0, 0)]") as message|null
if (!msg)
message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.")
return
cmd_admin_pm(whom, msg)
//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM.
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_admin_pm(whom, msg)
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
return
if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo
to_chat(src, "<span class='danger'>You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.</span>")
to_chat(src, "<span class='notice'>Message: [msg]</span>")
return
var/client/recipient
var/irc = 0
if(istext(whom))
if(cmptext(copytext(whom,1,2),"@"))
whom = findStealthKey(whom)
if(whom == "IRCKEY")
irc = 1
else
recipient = GLOB.directory[whom]
else if(istype(whom, /client))
recipient = whom
if(irc)
if(!ircreplyamount) //to prevent people from spamming irc
return
if(!msg)
msg = input(src,"Message:", "Private message to Administrator") as text|null
if(!msg)
return
if(holder)
to_chat(src, "<span class='danger'>Error: Use the admin IRC channel, nerd.</span>")
return
else
if(!recipient)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
if(msg)
to_chat(src, msg)
return
else if(msg) // you want to continue if there's no message instead of returning now
current_ticket.MessageNoRecipient(msg)
return
//get message text, limit it's length.and clean/escape html
if(!msg)
msg = input(src,"Message:", "Private message to [key_name(recipient, 0, 0)]") as message|null
msg = trim(msg)
if(!msg)
return
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
return
if(!recipient)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
else
current_ticket.MessageNoRecipient(msg)
return
if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
return
//clean the message if it's not sent by a high-rank admin
if(!check_rights(R_SERVER|R_DEBUG,0)||irc)//no sending html to the poor bots
msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN)))
if(!msg)
return
var/rawmsg = msg
if(holder)
msg = emoji_parse(msg)
var/keywordparsedmsg = keywords_lookup(msg)
if(irc)
to_chat(src, "<span class='notice'>PM to-<b>Admins</b>: <span class='linkify'>[rawmsg]</span></span>")
var/datum/admin_help/AH = admin_ticket_log(src, "<font color='red'>Reply PM from-<b>[key_name(src, TRUE, TRUE)] to <i>IRC</i>: [keywordparsedmsg]</font>")
ircreplyamount--
send2irc("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg)
else
if(recipient.holder)
if(holder) //both are admins
to_chat(recipient, "<span class='danger'>Admin PM from-<b>[key_name(src, recipient, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span></span>")
to_chat(src, "<span class='notice'>Admin PM to-<b>[key_name(recipient, src, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span></span>")
//omg this is dumb, just fill in both their tickets
var/interaction_message = "<font color='purple'>PM from-<b>[key_name(src, recipient, 1)]</b> to-<b>[key_name(recipient, src, 1)]</b>: [keywordparsedmsg]</font>"
admin_ticket_log(src, interaction_message)
if(recipient != src) //reeee
admin_ticket_log(recipient, interaction_message)
else //recipient is an admin but sender is not
var/replymsg = "Reply PM from-<b>[key_name(src, recipient, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span>"
admin_ticket_log(src, "<font color='red'>[replymsg]</font>")
to_chat(recipient, "<span class='danger'>[replymsg]</span>")
to_chat(src, "<span class='notice'>PM to-<b>Admins</b>: <span class='linkify'>[msg]</span></span>")
//play the receiving admin the adminhelp sound (if they have them enabled)
if(recipient.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
else
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
if(!recipient.current_ticket)
new /datum/admin_help(msg, recipient, TRUE)
to_chat(recipient, "<font color='red' size='4'><b>-- Administrator private message --</b></font>")
to_chat(recipient, "<span class='danger'>Admin PM from-<b>[key_name(src, recipient, 0)]</b>: <span class='linkify'>[msg]</span></span>")
to_chat(recipient, "<span class='danger'><i>Click on the administrator's name to reply.</i></span>")
to_chat(src, "<span class='notice'>Admin PM to-<b>[key_name(recipient, src, 1)]</b>: <span class='linkify'>[msg]</span></span>")
admin_ticket_log(recipient, "<font color='purple'>PM From [key_name_admin(src)]: [keywordparsedmsg]</font>")
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
//AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn
if(CONFIG_GET(flag/popup_admin_pm))
spawn() //so we don't hold the caller proc up
var/sender = src
var/sendername = key
var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|null //show message and await a reply
if(recipient && reply)
if(sender)
recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
else
adminhelp(reply) //sender has left, adminhelp instead
return
else //neither are admins
to_chat(src, "<span class='danger'>Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.</span>")
return
if(irc)
log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]")
for(var/client/X in GLOB.admins)
to_chat(X, "<span class='notice'><B>PM: [key_name(src, X, 0)]-&gt;IRC:</B> [keywordparsedmsg]</span>")
else
window_flash(recipient, ignorepref = TRUE)
log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]")
//we don't use message_admins here because the sender/receiver might get it too
for(var/client/X in GLOB.admins)
if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient
to_chat(X, "<span class='notice'><B>PM: [key_name(src, X, 0)]-&gt;[key_name(recipient, X, 0)]:</B> [keywordparsedmsg]</span>" )
#define IRC_AHELP_USAGE "Usage: ticket <close|resolve|icissue|reject|reopen \[ticket #\]|list>"
/proc/IrcPm(target,msg,sender)
target = ckey(target)
var/client/C = GLOB.directory[target]
var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target)
var/compliant_msg = trim(lowertext(msg))
var/irc_tagged = "[sender](IRC)"
var/list/splits = splittext(compliant_msg, " ")
if(splits.len && splits[1] == "ticket")
if(splits.len < 2)
return IRC_AHELP_USAGE
switch(splits[2])
if("close")
if(ticket)
ticket.Close(irc_tagged)
return "Ticket #[ticket.id] successfully closed"
if("resolve")
if(ticket)
ticket.Resolve(irc_tagged)
return "Ticket #[ticket.id] successfully resolved"
if("icissue")
if(ticket)
ticket.ICIssue(irc_tagged)
return "Ticket #[ticket.id] successfully marked as IC issue"
if("reject")
if(ticket)
ticket.Reject(irc_tagged)
return "Ticket #[ticket.id] successfully rejected"
if("reopen")
if(ticket)
return "Error: [target] already has ticket #[ticket.id] open"
var/fail = splits.len < 3 ? null : -1
if(!isnull(fail))
fail = text2num(splits[3])
if(isnull(fail))
return "Error: No/Invalid ticket id specified. [IRC_AHELP_USAGE]"
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail)
if(!AH)
return "Error: Ticket #[fail] not found"
if(AH.initiator_ckey != target)
return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]"
AH.Reopen()
return "Ticket #[ticket.id] successfully reopened"
if("list")
var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target)
if(!tickets.len)
return "None"
. = ""
for(var/I in tickets)
var/datum/admin_help/AH = I
if(.)
. += ", "
if(AH == ticket)
. += "Active: "
. += "#[AH.id]"
return
else
return IRC_AHELP_USAGE
return "Error: Ticket could not be found"
var/static/stealthkey
var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator"
if(!C)
return "Error: No client"
if(!stealthkey)
stealthkey = GenIrcStealthKey()
msg = sanitize(copytext(msg,1,MAX_MESSAGE_LEN))
if(!msg)
return "Error: No message"
message_admins("IRC message from [sender] to [key_name_admin(C)] : [msg]")
log_admin_private("IRC PM: [sender] -> [key_name(C)] : [msg]")
msg = emoji_parse(msg)
to_chat(C, "<font color='red' size='4'><b>-- Administrator private message --</b></font>")
to_chat(C, "<span class='adminsay'>Admin PM from-<b><a href='?priv_msg=[stealthkey]'>[adminname]</A></b>: [msg]</span>")
to_chat(C, "<span class='adminsay'><i>Click on the administrator's name to reply.</i></span>")
admin_ticket_log(C, "<font color='purple'>PM From [irc_tagged]: [msg]</font>")
window_flash(C, ignorepref = TRUE)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(C, 'sound/effects/adminhelp.ogg')
C.ircreplyamount = IRCREPLYCOUNT
return "Message Successful"
/proc/GenIrcStealthKey()
var/num = (rand(0,1000))
var/i = 0
while(i == 0)
i = 1
for(var/P in GLOB.stealthminID)
if(num == GLOB.stealthminID[P])
num++
i = 0
var/stealth = "@[num2text(num)]"
GLOB.stealthminID["IRCKEY"] = stealth
return stealth
#undef IRCREPLYCOUNT
+22
View File
@@ -0,0 +1,22 @@
/client/proc/cmd_admin_say(msg as text)
set category = "Special Verbs"
set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite
set hidden = 1
if(!check_rights(0))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
return
msg = emoji_parse(msg)
mob.log_talk(msg, LOG_ASAY)
msg = keywords_lookup(msg)
msg = "<span class='adminsay'><span class='prefix'>ADMIN:</span> <EM>[key_name(usr, 1)]</EM> [ADMIN_FLW(mob)]: <span class='message linkify'>[msg]</span></span>"
to_chat(GLOB.admins, msg)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/get_admin_say()
var/msg = input(src, null, "asay \"text\"") as text
cmd_admin_say(msg)
+22
View File
@@ -0,0 +1,22 @@
GLOBAL_VAR_INIT(terrorism, FALSE)
/client/proc/ak47s() // For when you just can't summon guns worthy of a firefight
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
return
GLOB.terrorism = TRUE
for(var/mob/living/carbon/human/H in GLOB.player_list)
if(H.stat == DEAD || !(H.client))
continue
H.make_terrorism()
send_to_playing_players("<span class='boldannounce'><font size=6>MOTHER RUSSIA ARMS THE MOB!</font></span>")
/mob/living/carbon/human/proc/make_terrorism()
for(var/obj/item/I in held_items)
qdel(I)
var/obj/item/gun/energy/laser/LaserAK/AK = new(src)
if(!GLOB.terrorism)
AK.flags_1 |= ADMIN_SPAWNED_1 //To prevent announcing
put_in_hands(AK)
AK.pickup(src) //For the stun shielding
+41
View File
@@ -0,0 +1,41 @@
/client/proc/atmosscan()
set category = "Mapping"
set name = "Check Plumbing"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//all plumbing - yes, some things might get stated twice, doesn't matter.
for (var/obj/machinery/atmospherics/plumbing in GLOB.machines)
if (plumbing.nodealert)
to_chat(usr, "Unconnected [plumbing.name] located at [ADMIN_VERBOSEJMP(plumbing)]")
//Manifolds
for (var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines)
if (!pipe.nodes[1] || !pipe.nodes[2] || !pipe.nodes[3])
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
//Pipes
for (var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines)
if (!pipe.nodes[1] || !pipe.nodes[2])
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
/client/proc/powerdebug()
set category = "Mapping"
set name = "Check Power"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
for (var/datum/powernet/PN in GLOB.powernets)
if (!PN.nodes || !PN.nodes.len)
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
to_chat(usr, "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]")
if (!PN.cables || (PN.cables.len < 10))
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]")
+26
View File
@@ -0,0 +1,26 @@
/client/proc/bluespace_artillery(mob/M in GLOB.mob_list)
if(!holder || !check_rights(R_FUN))
return
var/mob/living/target = M
if(!isliving(target))
to_chat(usr, "This can only be used on instances of type /mob/living")
return
explosion(target.loc, 0, 0, 0, 0)
var/turf/open/floor/T = get_turf(target)
if(istype(T))
if(prob(80))
T.break_tile_to_plating()
else
T.break_tile()
if(target.health <= 1)
target.gib(1, 1)
else
target.adjustBruteLoss(min(99,(target.health - 1)))
target.Knockdown(400)
target.stuttering = 20
+216
View File
@@ -0,0 +1,216 @@
/datum/admins/proc/open_borgopanel(borgo in GLOB.silicon_mobs)
set category = "Admin"
set name = "Show Borg Panel"
set desc = "Show borg panel"
if(!check_rights(R_ADMIN))
return
if (!istype(borgo, /mob/living/silicon/robot))
borgo = input("Select a borg", "Select a borg", null, null) as null|anything in GLOB.silicon_mobs
if (!istype(borgo, /mob/living/silicon/robot))
to_chat(usr, "<span class='warning'>Borg is required for borgpanel</span>")
var/datum/borgpanel/borgpanel = new(usr, borgo)
borgpanel.ui_interact(usr)
/datum/borgpanel
var/mob/living/silicon/robot/borg
var/user
/datum/borgpanel/New(to_user, mob/living/silicon/robot/to_borg)
if(!istype(to_borg))
CRASH("Borg panel is only available for borgs")
qdel(src)
user = CLIENT_FROM_VAR(to_user)
if (!user)
CRASH("Borg panel attempted to open to a mob without a client")
borg = to_borg
/datum/borgpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "borgopanel", "Borg Panel", 700, 700, master_ui, state)
ui.open()
/datum/borgpanel/ui_data(mob/user)
. = list()
.["borg"] = list(
"ref" = REF(borg),
"name" = "[borg]",
"emagged" = borg.emagged,
"active_module" = "[borg.module.type]",
"lawupdate" = borg.lawupdate,
"lockdown" = borg.lockcharge,
"scrambledcodes" = borg.scrambledcodes
)
.["upgrades"] = list()
for (var/upgradetype in subtypesof(/obj/item/borg/upgrade)-/obj/item/borg/upgrade/hypospray) //hypospray is a dummy parent for hypospray upgrades
var/obj/item/borg/upgrade/upgrade = upgradetype
if (initial(upgrade.module_type) && !istype(borg.module, initial(upgrade.module_type))) // Upgrade requires a different module
continue
var/installed = FALSE
if (locate(upgradetype) in borg)
installed = TRUE
.["upgrades"] += list(list("name" = initial(upgrade.name), "installed" = installed, "type" = upgradetype))
.["laws"] = borg.laws ? borg.laws.get_law_list(include_zeroth = TRUE) : list()
.["channels"] = list()
for (var/k in GLOB.radiochannels)
if (k == RADIO_CHANNEL_COMMON)
continue
.["channels"] += list(list("name" = k, "installed" = (k in borg.radio.channels)))
.["cell"] = borg.cell ? list("missing" = FALSE, "maxcharge" = borg.cell.maxcharge, "charge" = borg.cell.charge) : list("missing" = TRUE, "maxcharge" = 1, "charge" = 0)
.["modules"] = list()
for(var/moduletype in typesof(/obj/item/robot_module))
var/obj/item/robot_module/module = moduletype
.["modules"] += list(list(
"name" = initial(module.name),
"type" = "[module]"
))
.["ais"] = list(list("name" = "None", "ref" = "null", "connected" = isnull(borg.connected_ai)))
for(var/mob/living/silicon/ai/ai in GLOB.ai_list)
.["ais"] += list(list("name" = ai.name, "ref" = REF(ai), "connected" = (borg.connected_ai == ai)))
/datum/borgpanel/ui_act(action, params)
if(..())
return
switch (action)
if ("set_charge")
var/newcharge = input("New charge (0-[borg.cell.maxcharge]):", borg.name, borg.cell.charge) as num|null
if (newcharge)
borg.cell.charge = CLAMP(newcharge, 0, borg.cell.maxcharge)
message_admins("[key_name_admin(user)] set the charge of [ADMIN_LOOKUPFLW(borg)] to [borg.cell.charge].")
log_admin("[key_name(user)] set the charge of [key_name(borg)] to [borg.cell.charge].")
if ("remove_cell")
QDEL_NULL(borg.cell)
message_admins("[key_name_admin(user)] deleted the cell of [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] deleted the cell of [key_name(borg)].")
if ("change_cell")
var/chosen = pick_closest_path(null, make_types_fancy(typesof(/obj/item/stock_parts/cell)))
if (!ispath(chosen))
chosen = text2path(chosen)
if (chosen)
if (borg.cell)
QDEL_NULL(borg.cell)
var/new_cell = new chosen(borg)
borg.cell = new_cell
borg.cell.charge = borg.cell.maxcharge
borg.diag_hud_set_borgcell()
message_admins("[key_name_admin(user)] changed the cell of [ADMIN_LOOKUPFLW(borg)] to [new_cell].")
log_admin("[key_name(user)] changed the cell of [key_name(borg)] to [new_cell].")
if ("toggle_emagged")
borg.SetEmagged(!borg.emagged)
if (borg.emagged)
message_admins("[key_name_admin(user)] emagged [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] emagged [key_name(borg)].")
else
message_admins("[key_name_admin(user)] un-emagged [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] un-emagged [key_name(borg)].")
if ("toggle_lawupdate")
borg.lawupdate = !borg.lawupdate
if (borg.lawupdate)
message_admins("[key_name_admin(user)] enabled lawsync on [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] enabled lawsync on [key_name(borg)].")
else
message_admins("[key_name_admin(user)] disabled lawsync on [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] disabled lawsync on [key_name(borg)].")
if ("toggle_lockdown")
borg.SetLockdown(!borg.lockcharge)
if (borg.lockcharge)
message_admins("[key_name_admin(user)] locked down [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] locked down [key_name(borg)].")
else
message_admins("[key_name_admin(user)] released [ADMIN_LOOKUPFLW(borg)] from lockdown.")
log_admin("[key_name(user)] released [key_name(borg)] from lockdown.")
if ("toggle_scrambledcodes")
borg.scrambledcodes = !borg.scrambledcodes
if (borg.scrambledcodes)
message_admins("[key_name_admin(user)] enabled scrambled codes on [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] enabled scrambled codes on [key_name(borg)].")
else
message_admins("[key_name_admin(user)] disabled scrambled codes on [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] disabled scrambled codes on [key_name(borg)].")
if ("rename")
var/new_name = stripped_input(user,"What would you like to name this cyborg?","Input a name",borg.real_name,MAX_NAME_LEN)
if(!new_name)
return
message_admins("[key_name_admin(user)] renamed [ADMIN_LOOKUPFLW(borg)] to [new_name].")
log_admin("[key_name(user)] renamed [key_name(borg)] to [new_name].")
borg.fully_replace_character_name(borg.real_name,new_name)
if ("toggle_upgrade")
var/upgradepath = text2path(params["upgrade"])
var/obj/item/borg/upgrade/installedupgrade = locate(upgradepath) in borg
if (installedupgrade)
installedupgrade.deactivate(borg, user)
borg.upgrades -= installedupgrade
message_admins("[key_name_admin(user)] removed the [installedupgrade] upgrade from [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] removed the [installedupgrade] upgrade from [key_name(borg)].")
qdel(installedupgrade)
else
var/obj/item/borg/upgrade/upgrade = new upgradepath(borg)
upgrade.action(borg, user)
borg.upgrades += upgrade
message_admins("[key_name_admin(user)] added the [upgrade] borg upgrade to [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] added the [upgrade] borg upgrade to [key_name(borg)].")
if ("toggle_radio")
var/channel = params["channel"]
if (channel in borg.radio.channels) // We're removing a channel
if (!borg.radio.keyslot) // There's no encryption key. This shouldn't happen but we can cope
borg.radio.channels -= channel
if (channel == RADIO_CHANNEL_SYNDICATE)
borg.radio.syndie = FALSE
else if (channel == RADIO_CHANNEL_CENTCOM)
borg.radio.independent = FALSE
else
borg.radio.keyslot.channels -= channel
if (channel == RADIO_CHANNEL_SYNDICATE)
borg.radio.keyslot.syndie = FALSE
else if (channel == RADIO_CHANNEL_CENTCOM)
borg.radio.keyslot.independent = FALSE
message_admins("[key_name_admin(user)] removed the [channel] radio channel from [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] removed the [channel] radio channel from [key_name(borg)].")
else // We're adding a channel
if (!borg.radio.keyslot) // Assert that an encryption key exists
borg.radio.keyslot = new (borg.radio)
borg.radio.keyslot.channels[channel] = 1
if (channel == RADIO_CHANNEL_SYNDICATE)
borg.radio.keyslot.syndie = TRUE
else if (channel == RADIO_CHANNEL_CENTCOM)
borg.radio.keyslot.independent = TRUE
message_admins("[key_name_admin(user)] added the [channel] radio channel to [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] added the [channel] radio channel to [key_name(borg)].")
borg.radio.recalculateChannels()
if ("setmodule")
var/newmodulepath = text2path(params["module"])
if (ispath(newmodulepath))
borg.module.transform_to(newmodulepath)
message_admins("[key_name_admin(user)] changed the module of [ADMIN_LOOKUPFLW(borg)] to [newmodulepath].")
log_admin("[key_name(user)] changed the module of [key_name(borg)] to [newmodulepath].")
if ("slavetoai")
var/mob/living/silicon/ai/newai = locate(params["slavetoai"]) in GLOB.ai_list
if (newai && newai != borg.connected_ai)
borg.notify_ai(DISCONNECT)
if(borg.shell)
borg.undeploy()
borg.connected_ai = newai
borg.notify_ai(TRUE)
message_admins("[key_name_admin(user)] slaved [ADMIN_LOOKUPFLW(borg)] to the AI [ADMIN_LOOKUPFLW(newai)].")
log_admin("[key_name(user)] slaved [key_name(borg)] to the AI [key_name(newai)].")
else if (params["slavetoai"] == "null")
borg.notify_ai(DISCONNECT)
if(borg.shell)
borg.undeploy()
borg.connected_ai = null
message_admins("[key_name_admin(user)] freed [ADMIN_LOOKUPFLW(borg)] from being slaved to an AI.")
log_admin("[key_name(user)] freed [key_name(borg)] from being slaved to an AI.")
if (borg.lawupdate)
borg.lawsync()
. = TRUE
+11
View File
@@ -0,0 +1,11 @@
/client/proc/cinematic()
set name = "cinematic"
set category = "Fun"
set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
set hidden = 1
if(!SSticker)
return
var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in subtypesof(/datum/cinematic)
if(choice)
Cinematic(initial(choice.id),world,null)
+36
View File
@@ -0,0 +1,36 @@
/client/proc/dsay(msg as text)
set category = "Special Verbs"
set name = "Dsay"
set hidden = 1
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
if(!src.mob)
return
if(prefs.muted & MUTE_DEADCHAT)
to_chat(src, "<span class='danger'>You cannot send DSAY messages (muted).</span>")
return
if (src.handle_spam_prevention(msg,MUTE_DEADCHAT))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
mob.log_talk(msg, LOG_DSAY)
if (!msg)
return
var/static/nicknames = world.file2list("[global.config.directory]/admin_nicknames.txt")
var/rendered = "<span class='game deadsay'><span class='prefix'>DEAD:</span> <span class='name'>[uppertext(holder.rank)]([src.holder.fakekey ? pick(nicknames) : src.key])</span> says, <span class='message'>\"[emoji_parse(msg)]\"</span></span>"
for (var/mob/M in GLOB.player_list)
if(isnewplayer(M))
continue
if (M.stat == DEAD || (M.client && M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above
to_chat(M, rendered)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/get_dead_say()
var/msg = input(src, null, "dsay \"text\"") as text
dsay(msg)
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
/proc/show_air_status_to(turf/target, mob/user)
var/datum/gas_mixture/env = target.return_air()
var/list/env_gases = env.gases
var/burning = FALSE
if(isopenturf(target))
var/turf/open/T = target
if(T.active_hotspot)
burning = TRUE
var/list/lines = list("<span class='adminnotice'>[AREACOORD(target)]: [env.temperature] K ([env.temperature - T0C] C), [env.return_pressure()] kPa[(burning)?(", <font color='red'>burning</font>"):(null)]</span>")
for(var/id in env_gases)
var/moles = env_gases[id]
if (moles >= 0.00001)
lines += "[GLOB.meta_gas_names[id]]: [moles] mol"
to_chat(usr, lines.Join("\n"))
/client/proc/air_status(turf/target)
set category = "Debug"
set name = "Display Air Status"
if(!isturf(target))
return
show_air_status_to(target, usr)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Air Status") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/fix_next_move()
set category = "Debug"
set name = "Unfreeze Everyone"
var/largest_move_time = 0
var/largest_click_time = 0
var/mob/largest_move_mob = null
var/mob/largest_click_mob = null
for(var/mob/M in world)
if(!M.client)
continue
if(M.next_move >= largest_move_time)
largest_move_mob = M
if(M.next_move > world.time)
largest_move_time = M.next_move - world.time
else
largest_move_time = 1
if(M.next_click >= largest_click_time)
largest_click_mob = M
if(M.next_click > world.time)
largest_click_time = M.next_click - world.time
else
largest_click_time = 0
log_admin("DEBUG: [key_name(M)] next_move = [M.next_move] lastDblClick = [M.next_click] world.time = [world.time]")
M.next_move = 1
M.next_click = 0
message_admins("[ADMIN_LOOKUPFLW(largest_move_mob)] had the largest move delay with [largest_move_time] frames / [DisplayTimeText(largest_move_time)]!")
message_admins("[ADMIN_LOOKUPFLW(largest_click_mob)] had the largest click delay with [largest_click_time] frames / [DisplayTimeText(largest_click_time)]!")
message_admins("world.time = [world.time]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Unfreeze Everyone") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/proc/radio_report()
set category = "Debug"
set name = "Radio report"
var/output = "<b>Radio Report</b><hr>"
for (var/fq in SSradio.frequencies)
output += "<b>Freq: [fq]</b><br>"
var/list/datum/radio_frequency/fqs = SSradio.frequencies[fq]
if (!fqs)
output += "&nbsp;&nbsp;<b>ERROR</b><br>"
continue
for (var/filter in fqs.devices)
var/list/f = fqs.devices[filter]
if (!f)
output += "&nbsp;&nbsp;[filter]: ERROR<br>"
continue
output += "&nbsp;&nbsp;[filter]: [f.len]<br>"
for (var/device in f)
if (istype(device, /atom))
var/atom/A = device
output += "&nbsp;&nbsp;&nbsp;&nbsp;[device] ([AREACOORD(A)])<br>"
else
output += "&nbsp;&nbsp;&nbsp;&nbsp;[device]<br>"
usr << browse(output,"window=radioreport")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Radio Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/reload_admins()
set name = "Reload Admins"
set category = "Admin"
if(!src.holder)
return
var/confirm = alert(src, "Are you sure you want to reload all admins?", "Confirm", "Yes", "No")
if(confirm !="Yes")
return
load_admins()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reload All Admins") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
message_admins("[key_name_admin(usr)] manually reloaded admins")
+25
View File
@@ -0,0 +1,25 @@
/client/proc/roll_dices()
set category = "Fun"
set name = "Roll Dice"
if(!check_rights(R_FUN))
return
var/sum = input("How many times should we throw?") as num
var/side = input("Select the number of sides.") as num
if(!side)
side = 6
if(!sum)
sum = 2
var/dice = num2text(sum) + "d" + num2text(side)
if(alert("Do you want to inform the world about your game?",,"Yes", "No") == "Yes")
to_chat(world, "<h2 style=\"color:#A50400\">The dice have been rolled by Gods!</h2>")
var/result = roll(dice)
if(alert("Do you want to inform the world about the result?",,"Yes", "No") == "Yes")
to_chat(world, "<h2 style=\"color:#A50400\">Gods rolled [dice], result is [result]</h2>")
message_admins("[key_name_admin(src)] rolled dice [dice], result is [result]")
log_admin("[key_name_admin(src)] rolled dice [dice], result is [result]")
+26
View File
@@ -0,0 +1,26 @@
//replaces the old Ticklag verb, fps is easier to understand
/client/proc/set_server_fps()
set category = "Debug"
set name = "Set Server FPS"
set desc = "Sets game speed in frames-per-second. Can potentially break the game"
if(!check_rights(R_DEBUG))
return
var/cfg_fps = CONFIG_GET(number/fps)
var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null)
if(new_fps <= 0)
to_chat(src, "<span class='danger'>Error: set_server_fps(): Invalid world.fps value. No changes made.</span>")
return
if(new_fps > cfg_fps * 1.5)
if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm")
return
var/msg = "[key_name(src)] has modified world.fps to [new_fps]"
log_admin(msg, 0)
message_admins(msg, 0)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
CONFIG_SET(number/fps, new_fps)
world.fps = new_fps
+35
View File
@@ -0,0 +1,35 @@
//This proc allows download of past server logs saved within the data/logs/ folder.
/client/proc/getserverlogs()
set name = "Get Server Logs"
set desc = "View/retrieve logfiles."
set category = "Admin"
browseserverlogs()
/client/proc/getcurrentlogs()
set name = "Get Current Logs"
set desc = "View/retrieve logfiles for the current round."
set category = "Admin"
browseserverlogs("[GLOB.log_directory]/")
/client/proc/browseserverlogs(path = "data/logs/")
path = browse_files(path)
if(!path)
return
if(file_spam_check())
return
message_admins("[key_name_admin(src)] accessed file: [path]")
switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download"))
if ("View")
src << browse("<pre style='word-wrap: break-word;'>[html_encode(file2text(file(path)))]</pre>", list2params(list("window" = "viewfile.[path]")))
if ("Open")
src << run(file(path))
if ("Download")
src << ftp(file(path))
else
return
to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.")
return
@@ -0,0 +1,67 @@
/proc/show_individual_logging_panel(mob/M, source = LOGSRC_CLIENT, type = INDIVIDUAL_ATTACK_LOG)
if(!M || !ismob(M))
return
var/ntype = text2num(type)
//Add client links
var/dat = ""
if(M.client)
dat += "<center><p>Client</p></center>"
dat += "<center>"
dat += individual_logging_panel_link(M, INDIVIDUAL_ATTACK_LOG, LOGSRC_CLIENT, "Attack Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_SAY_LOG, LOGSRC_CLIENT, "Say Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_EMOTE_LOG, LOGSRC_CLIENT, "Emote Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_COMMS_LOG, LOGSRC_CLIENT, "Comms Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_OOC_LOG, LOGSRC_CLIENT, "OOC Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_SHOW_ALL_LOG, LOGSRC_CLIENT, "Show All", source, ntype)
dat += "</center>"
else
dat += "<p> No client attached to mob </p>"
dat += "<hr style='background:#000000; border:0; height:1px'>"
dat += "<center><p>Mob</p></center>"
//Add the links for the mob specific log
dat += "<center>"
dat += individual_logging_panel_link(M, INDIVIDUAL_ATTACK_LOG, LOGSRC_MOB, "Attack Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_SAY_LOG, LOGSRC_MOB, "Say Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_EMOTE_LOG, LOGSRC_MOB, "Emote Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_COMMS_LOG, LOGSRC_MOB, "Comms Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_OOC_LOG, LOGSRC_MOB, "OOC Log", source, ntype)
dat += " | "
dat += individual_logging_panel_link(M, INDIVIDUAL_SHOW_ALL_LOG, LOGSRC_MOB, "Show All", source, ntype)
dat += "</center>"
dat += "<hr style='background:#000000; border:0; height:1px'>"
var/log_source = M.logging;
if(source == LOGSRC_CLIENT && M.client) //if client doesn't exist just fall back to the mob log
log_source = M.client.player_details.logging //should exist, if it doesn't that's a bug, don't check for it not existing
for(var/log_type in log_source)
var/nlog_type = text2num(log_type)
if(nlog_type & ntype)
var/list/reversed = log_source[log_type]
if(islist(reversed))
reversed = reverseRange(reversed.Copy())
for(var/entry in reversed)
dat += "<font size=2px><b>[entry]</b><br>[reversed[entry]]</font><br>"
dat += "<hr>"
usr << browse(dat, "window=invidual_logging_[key_name(M)];size=600x480")
/proc/individual_logging_panel_link(mob/M, log_type, log_src, label, selected_src, selected_type)
var/slabel = label
if(selected_type == log_type && selected_src == log_src)
slabel = "<b>\[[label]\]</b>"
return "<a href='?_src_=holder;[HrefToken()];individuallog=[REF(M)];log_type=[log_type];log_src=[log_src]'>[slabel]</a>"
@@ -0,0 +1,13 @@
/proc/machine_upgrade(obj/machinery/M in world)
set name = "Tweak Component Ratings"
set category = "Debug"
if (!istype(M))
return
var/new_rating = input("Enter new rating:","Num") as num
if(new_rating && M.component_parts)
for(var/obj/item/stock_parts/P in M.component_parts)
P.rating = new_rating
M.RefreshParts()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Machine Upgrade", "[new_rating]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -0,0 +1,74 @@
/client/proc/manipulate_organs(mob/living/carbon/C in world)
set name = "Manipulate Organs"
set category = "Debug"
var/operation = input("Select organ operation.", "Organ Manipulation", "cancel") as null|anything in list("add organ", "add implant", "drop organ/implant", "remove organ/implant", "cancel")
if (!operation)
return
var/list/organs = list()
switch(operation)
if("add organ")
for(var/path in subtypesof(/obj/item/organ))
var/dat = replacetext("[path]", "/obj/item/organ/", ":")
organs[dat] = path
var/obj/item/organ/organ = input("Select organ type:", "Organ Manipulation", null) as null|anything in organs
if(!organ)
return
organ = organs[organ]
organ = new organ
organ.Insert(C)
log_admin("[key_name(usr)] has added organ [organ.type] to [key_name(C)]")
message_admins("[key_name_admin(usr)] has added organ [organ.type] to [ADMIN_LOOKUPFLW(C)]")
if("add implant")
for(var/path in subtypesof(/obj/item/implant))
var/dat = replacetext("[path]", "/obj/item/implant/", ":")
organs[dat] = path
var/obj/item/implant/organ = input("Select implant type:", "Organ Manipulation", null) as null|anything in organs
if(!organ)
return
organ = organs[organ]
organ = new organ
organ.implant(C)
log_admin("[key_name(usr)] has added implant [organ.type] to [key_name(C)]")
message_admins("[key_name_admin(usr)] has added implant [organ.type] to [ADMIN_LOOKUPFLW(C)]")
if("drop organ/implant", "remove organ/implant")
for(var/X in C.internal_organs)
var/obj/item/organ/I = X
organs["[I.name] ([I.type])"] = I
for(var/X in C.implants)
var/obj/item/implant/I = X
organs["[I.name] ([I.type])"] = I
var/obj/item/organ = input("Select organ/implant:", "Organ Manipulation", null) as null|anything in organs
if(!organ)
return
organ = organs[organ]
if(!organ)
return
var/obj/item/organ/O
var/obj/item/implant/I
log_admin("[key_name(usr)] has removed [organ.type] from [key_name(C)]")
message_admins("[key_name_admin(usr)] has removed [organ.type] from [ADMIN_LOOKUPFLW(C)]")
if(isorgan(organ))
O = organ
O.Remove(C)
else
I = organ
I.removed(C)
organ.forceMove(get_turf(C))
if(operation == "remove organ/implant")
qdel(organ)
else if(I) // Put the implant in case.
var/obj/item/implantcase/case = new(get_turf(C))
case.imp = I
I.forceMove(case)
case.update_icon()
@@ -0,0 +1,67 @@
/client/proc/map_template_load()
set category = "Debug"
set name = "Map template - Place"
var/datum/map_template/template
var/map = input(src, "Choose a Map Template to place at your CURRENT LOCATION","Place Map Template") as null|anything in SSmapping.map_templates
if(!map)
return
template = SSmapping.map_templates[map]
var/turf/T = get_turf(mob)
if(!T)
return
var/list/preview = list()
for(var/S in template.get_affected_turfs(T,centered = TRUE))
var/image/item = image('icons/turf/overlays.dmi',S,"greenOverlay")
item.plane = ABOVE_LIGHTING_PLANE
preview += item
images += preview
if(alert(src,"Confirm location.","Template Confirm","Yes","No") == "Yes")
if(template.load(T, centered = TRUE))
message_admins("<span class='adminnotice'>[key_name_admin(src)] has placed a map template ([template.name]) at [ADMIN_COORDJMP(T)]</span>")
else
to_chat(src, "Failed to place map")
images -= preview
/client/proc/map_template_upload()
set category = "Debug"
set name = "Map Template - Upload"
var/map = input(src, "Choose a Map Template to upload to template storage","Upload Map Template") as null|file
if(!map)
return
if(copytext("[map]",-4) != ".dmm")
to_chat(src, "<span class='warning'>Filename must end in '.dmm': [map]</span>")
return
var/datum/map_template/M
switch(alert(src, "What kind of map is this?", "Map type", "Normal", "Shuttle", "Cancel"))
if("Normal")
M = new /datum/map_template(map, "[map]", TRUE)
if("Shuttle")
M = new /datum/map_template/shuttle(map, "[map]", TRUE)
else
return
if(!M.cached_map)
to_chat(src, "<span class='warning'>Map template '[map]' failed to parse properly.</span>")
return
var/datum/map_report/report = M.cached_map.check_for_errors()
var/report_link
if(report)
report.show_to(src)
report_link = " - <a href='?src=[REF(report)];[HrefToken(TRUE)];show=1'>validation report</a>"
to_chat(src, "<span class='warning'>Map template '[map]' <a href='?src=[REF(report)];[HrefToken()];show=1'>failed validation</a>.</span>")
if(report.loadable)
var/response = alert(src, "The map failed validation, would you like to load it anyways?", "Map Errors", "Cancel", "Upload Anyways")
if(response != "Upload Anyways")
return
else
alert(src, "The map failed validation and cannot be loaded.", "Map Errors", "Oh Darn")
return
SSmapping.map_templates[M.name] = M
message_admins("<span class='adminnotice'>[key_name_admin(src)] has uploaded a map template '[map]' ([M.width]x[M.height])[report_link].</span>")
to_chat(src, "<span class='notice'>Map template '[map]' ready to place ([M.width]x[M.height])</span>")
+358
View File
@@ -0,0 +1,358 @@
//- Are all the floors with or without air, as they should be? (regular or airless)
//- Does the area have an APC?
//- Does the area have an Air Alarm?
//- Does the area have a Request Console?
//- Does the area have lights?
//- Does the area have a light switch?
//- Does the area have enough intercoms?
//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug)
//- Is the area connected to the scrubbers air loop?
//- Is the area connected to the vent air loop? (vent pumps)
//- Is everything wired properly?
//- Does the area have a fire alarm and firedoors?
//- Do all pod doors work properly?
//- Are accesses set properly on doors, pod buttons, etc.
//- Are all items placed properly? (not below vents, scrubbers, tables)
//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room?
//- Check for any misplaced or stacked piece of pipe (air and disposal)
//- Check for any misplaced or stacked piece of wire
//- Identify how hard it is to break into the area and where the weak points are
//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels.
GLOBAL_PROTECT(admin_verbs_debug_mapping)
GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/camera_view, //-errorage
/client/proc/sec_camera_report, //-errorage
/client/proc/intercom_view, //-errorage
/client/proc/air_status, //Air things
/client/proc/Cell, //More air things
/client/proc/atmosscan, //check plumbing
/client/proc/powerdebug, //check power
/client/proc/count_objects_on_z_level,
/client/proc/count_objects_all,
/client/proc/cmd_assume_direct_control, //-errorage
/client/proc/startSinglo,
/client/proc/set_server_fps, //allows you to set the ticklag.
/client/proc/cmd_admin_grantfullaccess,
/client/proc/cmd_admin_areatest_all,
/client/proc/cmd_admin_areatest_station,
/client/proc/cmd_admin_test_atmos_controllers,
/client/proc/cmd_admin_rejuvenate,
/datum/admins/proc/show_traitor_panel,
/client/proc/disable_communication,
/client/proc/cmd_show_at_list,
/client/proc/cmd_show_at_markers,
/client/proc/manipulate_organs,
/client/proc/start_line_profiling,
/client/proc/stop_line_profiling,
/client/proc/show_line_profiling,
/client/proc/create_mapping_job_icons,
/client/proc/debug_z_levels,
/client/proc/place_ruin
))
/obj/effect/debugging/mapfix_marker
name = "map fix marker"
icon = 'icons/mob/screen_gen.dmi'
icon_state = "mapfixmarker"
desc = "I am a mappers mistake."
/obj/effect/debugging/marker
icon = 'icons/turf/areas.dmi'
icon_state = "yellow"
/obj/effect/debugging/marker/Move()
return 0
/client/proc/camera_view()
set category = "Mapping"
set name = "Camera Range Display"
var/on = FALSE
for(var/turf/T in world)
if(T.maptext)
on = TRUE
T.maptext = null
if(!on)
var/list/seen = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
for(var/turf/T in C.can_see())
seen[T]++
for(var/turf/T in seen)
T.maptext = "[seen[T]]"
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range")
/client/proc/sec_camera_report()
set category = "Mapping"
set name = "Camera Report"
if(!Master)
alert(usr,"Master_controller not found.","Sec Camera Report")
return 0
var/list/obj/machinery/camera/CL = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
CL += C
var/output = {"<B>Camera Abnormalities Report</B><HR>
<B>The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.</B><BR><ul>"}
for(var/obj/machinery/camera/C1 in CL)
for(var/obj/machinery/camera/C2 in CL)
if(C1 != C2)
if(C1.c_tag == C2.c_tag)
output += "<li><font color='red'>c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]</font></li>"
if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y)
output += "<li><font color='red'>FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</font></li>"
if(C1.loc == C2.loc)
output += "<li>Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</li>"
var/turf/T = get_step(C1,turn(C1.dir,180))
if(!T || !isturf(T) || !T.density )
if(!(locate(/obj/structure/grille) in T))
var/window_check = 0
for(var/obj/structure/window/W in T)
if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) )
window_check = 1
break
if(!window_check)
output += "<li><font color='red'>Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]</font></li>"
output += "</ul>"
usr << browse(output,"window=airreport;size=1000x500")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/intercom_view()
set category = "Mapping"
set name = "Intercom Range Display"
var/static/intercom_range_display_status = FALSE
intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something
for(var/obj/effect/debugging/marker/M in world)
qdel(M)
if(intercom_range_display_status)
for(var/obj/item/radio/intercom/I in world)
for(var/turf/T in orange(7,I))
var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T)
if (!(F in view(7,I.loc)))
qdel(F)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_show_at_list()
set category = "Mapping"
set name = "Show roundstart AT list"
set desc = "Displays a list of active turfs coordinates at roundstart"
var/dat = {"<b>Coordinate list of Active Turfs at Roundstart</b>
<br>Real-time Active Turfs list you can see in Air Subsystem at active_turfs var<br>"}
for(var/t in GLOB.active_turfs_startlist)
var/turf/T = t
dat += "[ADMIN_VERBOSEJMP(T)]\n"
dat += "<br>"
usr << browse(dat, "window=at_list")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_show_at_markers()
set category = "Mapping"
set name = "Show roundstart AT markers"
set desc = "Places a marker on all active-at-roundstart turfs"
var/count = 0
for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers)
qdel(AT)
count++
if(count)
to_chat(usr, "[count] AT markers removed.")
else
for(var/t in GLOB.active_turfs_startlist)
new /obj/effect/abstract/marker/at(t)
count++
to_chat(usr, "[count] AT markers placed.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers")
/client/proc/enable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Enable"
if(!check_rights(R_DEBUG))
return
verbs -= /client/proc/enable_debug_verbs
verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/disable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Disable"
verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
verbs += /client/proc/enable_debug_verbs
SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_on_z_level()
set category = "Mapping"
set name = "Count Objects On Level"
var/level = input("Which z-level?","Level?") as text
if(!level)
return
var/num_level = text2num(level)
if(!num_level)
return
if(!isnum(num_level))
return
var/type_text = input("Which type path?","Path?") as text
if(!type_text)
return
var/type_path = text2path(type_text)
if(!type_path)
return
var/count = 0
var/list/atom/atom_list = list()
for(var/atom/A in world)
if(istype(A,type_path))
var/atom/B = A
while(!(isturf(B.loc)))
if(B && B.loc)
B = B.loc
else
break
if(B)
if(B.z == num_level)
count++
atom_list += A
to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_all()
set category = "Mapping"
set name = "Count Objects All"
var/type_text = input("Which type path?","") as text
if(!type_text)
return
var/type_path = text2path(type_text)
if(!type_path)
return
var/count = 0
for(var/atom/A in world)
if(istype(A,type_path))
count++
to_chat(world, "There are [count] objects of type [type_path] in the game world")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//This proc is intended to detect lag problems relating to communication procs
GLOBAL_VAR_INIT(say_disabled, FALSE)
/client/proc/disable_communication()
set category = "Mapping"
set name = "Disable all communication verbs"
GLOB.say_disabled = !GLOB.say_disabled
if(GLOB.say_disabled)
message_admins("[key] used 'Disable all communication verbs', killing all communication methods.")
else
message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.")
//This generates the icon states for job starting location landmarks.
/client/proc/create_mapping_job_icons()
set name = "Generate job landmarks icons"
set category = "Mapping"
var/icon/final = icon()
var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted
D.setDir(SOUTH)
for(var/job in subtypesof(/datum/job))
var/datum/job/JB = new job
switch(JB.title)
if("AI")
final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI")
if("Cyborg")
final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg")
else
for(var/obj/item/I in D)
qdel(I)
randomize_human(D)
JB.equip(D, TRUE, FALSE)
COMPILE_OVERLAYS(D)
var/icon/I = icon(getFlatIcon(D), frame = 1)
final.Insert(I, JB.title)
qdel(D)
//Also add the x
for(var/x_number in 1 to 4)
final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]")
fcopy(final, "icons/mob/landmarks.dmi")
/client/proc/debug_z_levels()
set name = "Debug Z-Levels"
set category = "Mapping"
var/list/z_list = SSmapping.z_list
var/list/messages = list()
messages += "<b>World</b>: [world.maxx] x [world.maxy] x [world.maxz]<br>"
var/list/linked_levels = list()
var/min_x = INFINITY
var/min_y = INFINITY
var/max_x = -INFINITY
var/max_y = -INFINITY
for(var/z in 1 to max(world.maxz, z_list.len))
if (z > z_list.len)
messages += "<b>[z]</b>: Unmanaged (out of bounds)<br>"
continue
var/datum/space_level/S = z_list[z]
if (!S)
messages += "<b>[z]</b>: Unmanaged (null)<br>"
continue
var/linkage
switch (S.linkage)
if (UNAFFECTED)
linkage = "no linkage"
if (SELFLOOPING)
linkage = "self-looping"
if (CROSSLINKED)
linkage = "linked at ([S.xi], [S.yi])"
linked_levels += S
min_x = min(min_x, S.xi)
min_y = min(min_y, S.yi)
max_x = max(max_x, S.xi)
max_y = max(max_y, S.yi)
else
linkage = "unknown linkage '[S.linkage]'"
messages += "<b>[z]</b>: [S.name], [linkage], traits: [json_encode(S.traits)]<br>"
if (S.z_value != z)
messages += "-- z_value is [S.z_value], should be [z]<br>"
if (S.name == initial(S.name))
messages += "-- name not set<br>"
if (z > world.maxz)
messages += "-- exceeds max z"
var/grid[max_x - min_x + 1][max_y - min_y + 1]
for(var/datum/space_level/S in linked_levels)
grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value
messages += "<table border='1'>"
for(var/y in max_y to min_y step -1)
var/list/part = list()
for(var/x in min_x to max_x)
part += "[grid[x - min_x + 1][y - min_y + 1]]"
messages += "<tr><td>[part.Join("</td><td>")]</td></tr>"
messages += "</table>"
to_chat(src, messages.Join(""))
+44
View File
@@ -0,0 +1,44 @@
/client/proc/forcerandomrotate()
set category = "Server"
set name = "Trigger Random Map Rotation"
var/rotate = alert("Force a random map rotation to trigger?", "Rotate map?", "Yes", "Cancel")
if (rotate != "Yes")
return
message_admins("[key_name_admin(usr)] is forcing a random map rotation.")
log_admin("[key_name(usr)] is forcing a random map rotation.")
SSticker.maprotatechecked = 1
SSmapping.maprotate()
/client/proc/adminchangemap()
set category = "Server"
set name = "Change Map"
var/list/maprotatechoices = list()
for (var/map in config.maplist)
var/datum/map_config/VM = config.maplist[map]
var/mapname = VM.map_name
if (VM == config.defaultmap)
mapname += " (Default)"
if (VM.config_min_users > 0 || VM.config_max_users > 0)
mapname += " \["
if (VM.config_min_users > 0)
mapname += "[VM.config_min_users]"
else
mapname += "0"
mapname += "-"
if (VM.config_max_users > 0)
mapname += "[VM.config_max_users]"
else
mapname += "inf"
mapname += "\]"
maprotatechoices[mapname] = VM
var/chosenmap = input("Choose a map to change to", "Change Map") as null|anything in maprotatechoices
if (!chosenmap)
return
SSticker.maprotatechecked = 1
var/datum/map_config/VM = maprotatechoices[chosenmap]
message_admins("[key_name_admin(usr)] is changing the map to [VM.map_name]")
log_admin("[key_name(usr)] is changing the map to [VM.map_name]")
if (SSmapping.changemap(VM) == 0)
message_admins("[key_name_admin(usr)] has changed the map to [VM.map_name]")
+265
View File
@@ -0,0 +1,265 @@
/client/proc/cmd_mass_modify_object_variables(atom/A, var_name)
set category = "Debug"
set name = "Mass Edit Variables"
set desc="(target) Edit all instances of a target item's variables"
var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL items)
if(!check_rights(R_VAREDIT))
return
if(A && A.type)
method = vv_subtype_prompt(A.type)
src.massmodify_variables(A, var_name, method)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Edit Variables") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/massmodify_variables(datum/O, var_name = "", method = 0)
if(!check_rights(R_VAREDIT))
return
if(!istype(O))
return
var/variable = ""
if(!var_name)
var/list/names = list()
for (var/V in O.vars)
names += V
names = sortList(names)
variable = input("Which var?", "Var") as null|anything in names
else
variable = var_name
if(!variable || !O.can_vv_get(variable))
return
var/default
var/var_value = O.vars[variable]
if(variable in GLOB.VVckey_edit)
to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.")
return
if(variable in GLOB.VVlocked)
if(!check_rights(R_DEBUG))
return
if(variable in GLOB.VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG))
return
if(variable in GLOB.VVpixelmovement)
if(!check_rights(R_DEBUG))
return
var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT")
if (prompt != "Continue")
return
default = vv_get_class(variable, var_value)
if(isnull(default))
to_chat(src, "Unable to determine variable type.")
else
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable contains: [var_value]")
if(default == VV_NUM)
var/dir_text = ""
if(var_value > 0 && var_value < 16)
if(var_value & 1)
dir_text += "NORTH"
if(var_value & 2)
dir_text += "SOUTH"
if(var_value & 4)
dir_text += "EAST"
if(var_value & 8)
dir_text += "WEST"
if(dir_text)
to_chat(src, "If a direction, direction is: [dir_text]")
var/value = vv_get_value(default_class = default)
var/new_value = value["value"]
var/class = value["class"]
if(!class || !new_value == null && class != VV_NULL)
return
if (class == VV_MESSAGE)
class = VV_TEXT
if (value["type"])
class = VV_NEW_TYPE
var/original_name = "[O]"
var/rejected = 0
var/accepted = 0
switch(class)
if(VV_RESTORE_DEFAULT)
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if (D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE)
accepted++
else
rejected++
CHECK_TICK
if(VV_TEXT)
var/list/varsvars = vv_parse_text(O, new_value)
var/pre_processing = new_value
var/unique
if (varsvars && varsvars.len)
unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same")
if(unique == "Unique")
unique = TRUE
else
unique = FALSE
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]")
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if(unique)
new_value = pre_processing
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]")
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
CHECK_TICK
if (VV_NEW_TYPE)
var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel")
if (many == "Cancel")
return
if (many == "Many")
many = TRUE
else
many = FALSE
var/type = value["type"]
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if(many && !new_value)
new_value = new type()
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
new_value = null
CHECK_TICK
else
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
CHECK_TICK
var/count = rejected+accepted
if (!count)
to_chat(src, "No objects found")
return
if (!accepted)
to_chat(src, "Every object rejected your edit")
return
if (rejected)
to_chat(src, "[rejected] out of [count] objects rejected your edit")
log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])")
log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)")
message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)")
/proc/get_all_of_type(var/T, subtypes = TRUE)
var/list/typecache = list()
typecache[T] = 1
if (subtypes)
typecache = typecacheof(typecache)
. = list()
if (ispath(T, /mob))
for(var/mob/thing in GLOB.mob_list)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj/machinery/door))
for(var/obj/machinery/door/thing in GLOB.airlocks)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj/machinery))
for(var/obj/machinery/thing in GLOB.machines)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj))
for(var/obj/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /atom/movable))
for(var/atom/movable/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /turf))
for(var/turf/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /atom))
for(var/atom/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /client))
for(var/client/thing in GLOB.clients)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /datum))
for(var/datum/thing)
if (typecache[thing.type])
. += thing
CHECK_TICK
else
for(var/datum/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
+644
View File
@@ -0,0 +1,644 @@
GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending"))
GLOBAL_PROTECT(VVlocked)
GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize"))
GLOBAL_PROTECT(VVicon_edit_lock)
GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey"))
GLOBAL_PROTECT(VVckey_edit)
GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y"))
GLOBAL_PROTECT(VVpixelmovement)
/client/proc/vv_get_class(var/var_name, var/var_value)
if(isnull(var_value))
. = VV_NULL
else if (isnum(var_value))
if (var_name in GLOB.bitfields)
. = VV_BITFIELD
else
. = VV_NUM
else if (istext(var_value))
if (findtext(var_value, "\n"))
. = VV_MESSAGE
else
. = VV_TEXT
else if (isicon(var_value))
. = VV_ICON
else if (ismob(var_value))
. = VV_MOB_REFERENCE
else if (isloc(var_value))
. = VV_ATOM_REFERENCE
else if (istype(var_value, /client))
. = VV_CLIENT
else if (istype(var_value, /datum))
. = VV_DATUM_REFERENCE
else if (ispath(var_value))
if (ispath(var_value, /atom))
. = VV_ATOM_TYPE
else if (ispath(var_value, /datum))
. = VV_DATUM_TYPE
else
. = VV_TYPE
else if (islist(var_value))
. = VV_LIST
else if (isfile(var_value))
. = VV_FILE
else
. = VV_NULL
/client/proc/vv_get_value(class, default_class, current_value, list/restricted_classes, list/extra_classes, list/classes, var_name)
. = list("class" = class, "value" = null)
if (!class)
if (!classes)
classes = list (
VV_NUM,
VV_TEXT,
VV_MESSAGE,
VV_ICON,
VV_ATOM_REFERENCE,
VV_DATUM_REFERENCE,
VV_MOB_REFERENCE,
VV_CLIENT,
VV_ATOM_TYPE,
VV_DATUM_TYPE,
VV_TYPE,
VV_FILE,
VV_NEW_ATOM,
VV_NEW_DATUM,
VV_NEW_TYPE,
VV_NEW_LIST,
VV_NULL,
VV_RESTORE_DEFAULT
)
if(holder && holder.marked_datum && !(VV_MARKED_DATUM in restricted_classes))
classes += "[VV_MARKED_DATUM] ([holder.marked_datum.type])"
if (restricted_classes)
classes -= restricted_classes
if (extra_classes)
classes += extra_classes
.["class"] = input(src, "What kind of data?", "Variable Type", default_class) as null|anything in classes
if (holder && holder.marked_datum && .["class"] == "[VV_MARKED_DATUM] ([holder.marked_datum.type])")
.["class"] = VV_MARKED_DATUM
switch(.["class"])
if (VV_TEXT)
.["value"] = input("Enter new text:", "Text", current_value) as null|text
if (.["value"] == null)
.["class"] = null
return
if (VV_MESSAGE)
.["value"] = input("Enter new text:", "Text", current_value) as null|message
if (.["value"] == null)
.["class"] = null
return
if (VV_NUM)
.["value"] = input("Enter new number:", "Num", current_value) as null|num
if (.["value"] == null)
.["class"] = null
return
if (VV_BITFIELD)
.["value"] = input_bitfield(usr, "Editing bitfield: [var_name]", var_name, current_value)
if (.["value"] == null)
.["class"] = null
return
if (VV_ATOM_TYPE)
.["value"] = pick_closest_path(FALSE)
if (.["value"] == null)
.["class"] = null
return
if (VV_DATUM_TYPE)
.["value"] = pick_closest_path(FALSE, get_fancy_list_of_datum_types())
if (.["value"] == null)
.["class"] = null
return
if (VV_TYPE)
var/type = current_value
var/error = ""
do
type = input("Enter type:[error]", "Type", type) as null|text
if (!type)
break
type = text2path(type)
error = "\nType not found, Please try again"
while(!type)
if (!type)
.["class"] = null
return
.["value"] = type
if (VV_ATOM_REFERENCE)
var/type = pick_closest_path(FALSE)
var/subtypes = vv_subtype_prompt(type)
if (subtypes == null)
.["class"] = null
return
var/list/things = vv_reference_list(type, subtypes)
var/value = input("Select reference:", "Reference", current_value) as null|anything in things
if (!value)
.["class"] = null
return
.["value"] = things[value]
if (VV_DATUM_REFERENCE)
var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types())
var/subtypes = vv_subtype_prompt(type)
if (subtypes == null)
.["class"] = null
return
var/list/things = vv_reference_list(type, subtypes)
var/value = input("Select reference:", "Reference", current_value) as null|anything in things
if (!value)
.["class"] = null
return
.["value"] = things[value]
if (VV_MOB_REFERENCE)
var/type = pick_closest_path(FALSE, make_types_fancy(typesof(/mob)))
var/subtypes = vv_subtype_prompt(type)
if (subtypes == null)
.["class"] = null
return
var/list/things = vv_reference_list(type, subtypes)
var/value = input("Select reference:", "Reference", current_value) as null|anything in things
if (!value)
.["class"] = null
return
.["value"] = things[value]
if (VV_CLIENT)
.["value"] = input("Select reference:", "Reference", current_value) as null|anything in GLOB.clients
if (.["value"] == null)
.["class"] = null
return
if (VV_FILE)
.["value"] = input("Pick file:", "File") as null|file
if (.["value"] == null)
.["class"] = null
return
if (VV_ICON)
.["value"] = input("Pick icon:", "Icon") as null|icon
if (.["value"] == null)
.["class"] = null
return
if (VV_MARKED_DATUM)
.["value"] = holder.marked_datum
if (.["value"] == null)
.["class"] = null
return
if (VV_NEW_ATOM)
var/type = pick_closest_path(FALSE)
if (!type)
.["class"] = null
return
.["type"] = type
var/atom/newguy = new type()
newguy.datum_flags |= DF_VAR_EDITED
.["value"] = newguy
if (VV_NEW_DATUM)
var/type = pick_closest_path(FALSE, get_fancy_list_of_datum_types())
if (!type)
.["class"] = null
return
.["type"] = type
var/datum/newguy = new type()
newguy.datum_flags |= DF_VAR_EDITED
.["value"] = newguy
if (VV_NEW_TYPE)
var/type = current_value
var/error = ""
do
type = input("Enter type:[error]", "Type", type) as null|text
if (!type)
break
type = text2path(type)
error = "\nType not found, Please try again"
while(!type)
if (!type)
.["class"] = null
return
.["type"] = type
var/datum/newguy = new type()
if(istype(newguy))
newguy.datum_flags |= DF_VAR_EDITED
.["value"] = newguy
if (VV_NEW_LIST)
.["value"] = list()
.["type"] = /list
/client/proc/vv_parse_text(O, new_var)
if(O && findtext(new_var,"\["))
var/process_vars = alert(usr,"\[] detected in string, process as variables?","Process Variables?","Yes","No")
if(process_vars == "Yes")
. = string2listofvars(new_var, O)
//do they want you to include subtypes?
//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes)
//TRUE = Yes subtypes
//NULL = User cancelled at the prompt or invalid type given
/client/proc/vv_subtype_prompt(var/type)
if (!ispath(type))
return
var/list/subtypes = subtypesof(type)
if (!subtypes || !subtypes.len)
return FALSE
if (subtypes && subtypes.len)
switch(alert("Strict object type detection?", "Type detection", "Strictly this type","This type and subtypes", "Cancel"))
if("Strictly this type")
return FALSE
if("This type and subtypes")
return TRUE
else
return
/client/proc/vv_reference_list(type, subtypes)
. = list()
var/list/types = list(type)
if (subtypes)
types = typesof(type)
var/list/fancytypes = make_types_fancy(types)
for(var/fancytype in fancytypes) //swap the assoication
types[fancytypes[fancytype]] = fancytype
var/things = get_all_of_type(type, subtypes)
var/i = 0
for(var/thing in things)
var/datum/D = thing
i++
//try one of 3 methods to shorten the type text:
// fancy type,
// fancy type with the base type removed from the begaining,
// the type with the base type removed from the begaining
var/fancytype = types[D.type]
if (findtext(fancytype, types[type]))
fancytype = copytext(fancytype, lentext(types[type])+1)
var/shorttype = copytext("[D.type]", lentext("[type]")+1)
if (lentext(shorttype) > lentext(fancytype))
shorttype = fancytype
if (!lentext(shorttype))
shorttype = "/"
.["[D]([shorttype])[REF(D)]#[i]"] = D
/client/proc/mod_list_add_ass(atom/O) //hehe
var/list/L = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
var/class = L["class"]
if (!class)
return
var/var_value = L["value"]
if(class == VV_TEXT || class == VV_MESSAGE)
var/list/varsvars = vv_parse_text(O, var_value)
for(var/V in varsvars)
var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]")
return var_value
/client/proc/mod_list_add(list/L, atom/O, original_name, objectvar)
var/list/LL = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
var/class = LL["class"]
if (!class)
return
var/var_value = LL["value"]
if(class == VV_TEXT || class == VV_MESSAGE)
var/list/varsvars = vv_parse_text(O, var_value)
for(var/V in varsvars)
var_value = replacetext(var_value,"\[[V]]","[O.vars[V]]")
if (O)
L = L.Copy()
L += var_value
switch(alert("Would you like to associate a value with the list entry?",,"Yes","No"))
if("Yes")
L[var_value] = mod_list_add_ass(O) //hehe
if (O)
if (O.vv_edit_var(objectvar, L) == FALSE)
to_chat(src, "Your edit was rejected by the object.")
return
log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]")
message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]")
/client/proc/mod_list(list/L, atom/O, original_name, objectvar, index, autodetect_class = FALSE)
if(!check_rights(R_VAREDIT))
return
if(!istype(L, /list))
to_chat(src, "Not a List.")
return
if(L.len > 1000)
var/confirm = alert(src, "The list you're trying to edit is very long, continuing may crash the server.", "Warning", "Continue", "Abort")
if(confirm != "Continue")
return
var/list/names = list()
for (var/i in 1 to L.len)
var/key = L[i]
var/value
if (IS_NORMAL_LIST(L) && !isnum(key))
value = L[key]
if (value == null)
value = "null"
names["#[i] [key] = [value]"] = i
if (!index)
var/variable = input("Which var?","Var") as null|anything in names + "(ADD VAR)" + "(CLEAR NULLS)" + "(CLEAR DUPES)" + "(SHUFFLE)"
if(variable == null)
return
if(variable == "(ADD VAR)")
mod_list_add(L, O, original_name, objectvar)
return
if(variable == "(CLEAR NULLS)")
L = L.Copy()
listclearnulls(L)
if (!O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS")
message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR NULLS")
return
if(variable == "(CLEAR DUPES)")
L = uniqueList(L)
if (!O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES")
message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: CLEAR DUPES")
return
if(variable == "(SHUFFLE)")
L = shuffle(L)
if (!O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE")
message_admins("[key_name_admin(src)] modified [original_name]'s list [objectvar]: SHUFFLE")
return
index = names[variable]
var/assoc_key
if (index == null)
return
var/assoc = 0
var/prompt = alert(src, "Do you want to edit the key or its assigned value?", "Associated List", "Key", "Assigned Value", "Cancel")
if (prompt == "Cancel")
return
if (prompt == "Assigned Value")
assoc = 1
assoc_key = L[index]
var/default
var/variable
if (assoc)
variable = L[assoc_key]
else
variable = L[index]
default = vv_get_class(objectvar, variable)
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable contains: [variable]")
if(default == VV_NUM)
var/dir_text = ""
var/tdir = variable
if(tdir > 0 && tdir < 16)
if(tdir & 1)
dir_text += "NORTH"
if(tdir & 2)
dir_text += "SOUTH"
if(tdir & 4)
dir_text += "EAST"
if(tdir & 8)
dir_text += "WEST"
if(dir_text)
to_chat(usr, "If a direction, direction is: [dir_text]")
var/original_var = variable
if (O)
L = L.Copy()
var/class
if(autodetect_class)
if (default == VV_TEXT)
default = VV_MESSAGE
class = default
var/list/LL = vv_get_value(default_class = default, current_value = original_var, restricted_classes = list(VV_RESTORE_DEFAULT), extra_classes = list(VV_LIST, "DELETE FROM LIST"))
class = LL["class"]
if (!class)
return
var/new_var = LL["value"]
if(class == VV_MESSAGE)
class = VV_TEXT
switch(class) //Spits a runtime error if you try to modify an entry in the contents list. Dunno how to fix it, yet.
if(VV_LIST)
mod_list(variable, O, original_name, objectvar)
if("DELETE FROM LIST")
L.Cut(index, index+1)
if (O)
if (O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]")
message_admins("[key_name_admin(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]")
return
if(VV_TEXT)
var/list/varsvars = vv_parse_text(O, new_var)
for(var/V in varsvars)
new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]")
if(assoc)
L[assoc_key] = new_var
else
L[index] = new_var
if (O)
if (O.vv_edit_var(objectvar, L) == FALSE)
to_chat(src, "Your edit was rejected by the object.")
return
log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]")
message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]")
/proc/vv_varname_lockcheck(param_var_name)
if(param_var_name in GLOB.VVlocked)
if(!check_rights(R_DEBUG))
return FALSE
if(param_var_name in GLOB.VVckey_edit)
if(!check_rights(R_SPAWN|R_DEBUG))
return FALSE
if(param_var_name in GLOB.VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG))
return FALSE
if(param_var_name in GLOB.VVpixelmovement)
if(!check_rights(R_DEBUG))
return FALSE
var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT")
if (prompt != "Continue")
return FALSE
return TRUE
/client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0)
if(!check_rights(R_VAREDIT))
return
var/class
var/variable
var/var_value
if(param_var_name)
if(!param_var_name in O.vars)
to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])")
return
variable = param_var_name
else
var/list/names = list()
for (var/V in O.vars)
names += V
names = sortList(names)
variable = input("Which var?","Var") as null|anything in names
if(!variable)
return
if(!O.can_vv_get(variable))
return
var_value = O.vars[variable]
if(!vv_varname_lockcheck(variable))
return
if(istype(O, /datum/armor))
var/prompt = alert(src, "Editing this var changes this value on potentially thousands of items that share the same combination of armor values. If you want to edit the armor of just one item, use the \"Modify armor values\" dropdown item", "DANGER", "ABORT ", "Continue", " ABORT")
if (prompt != "Continue")
return
var/default = vv_get_class(variable, var_value)
if(isnull(default))
to_chat(src, "Unable to determine variable type.")
else
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable contains: [var_value]")
if(default == VV_NUM)
var/dir_text = ""
if(var_value > 0 && var_value < 16)
if(var_value & 1)
dir_text += "NORTH"
if(var_value & 2)
dir_text += "SOUTH"
if(var_value & 4)
dir_text += "EAST"
if(var_value & 8)
dir_text += "WEST"
if(dir_text)
to_chat(src, "If a direction, direction is: [dir_text]")
if(autodetect_class && default != VV_NULL)
if (default == VV_TEXT)
default = VV_MESSAGE
class = default
var/list/value = vv_get_value(class, default, var_value, extra_classes = list(VV_LIST), var_name = variable)
class = value["class"]
if (!class)
return
var/var_new = value["value"]
if(class == VV_MESSAGE)
class = VV_TEXT
var/original_name = "[O]"
switch(class)
if(VV_LIST)
if(!islist(var_value))
mod_list(list(), O, original_name, variable)
mod_list(var_value, O, original_name, variable)
return
if(VV_RESTORE_DEFAULT)
var_new = initial(O.vars[variable])
if(VV_TEXT)
var/list/varsvars = vv_parse_text(O, var_new)
for(var/V in varsvars)
var_new = replacetext(var_new,"\[[V]]","[O.vars[V]]")
if (O.vv_edit_var(variable, var_new) == FALSE)
to_chat(src, "Your edit was rejected by the object.")
return
vv_update_display(O, "varedited", VV_MSG_EDITED)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VAR_EDIT, args)
log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]")
log_admin("[key_name(src)] modified [original_name]'s [variable] from [html_encode("[var_value]")] to [html_encode("[var_new]")]")
var/msg = "[key_name_admin(src)] modified [original_name]'s [variable] from [var_value] to [var_new]"
message_admins(msg)
admin_ticket_log(O, msg)
return TRUE
+458
View File
@@ -0,0 +1,458 @@
/client/proc/one_click_antag()
set name = "Create Antagonist"
set desc = "Auto-create an antagonist of your choice"
set category = "Admin"
if(holder)
holder.one_click_antag()
return
/datum/admins/proc/one_click_antag()
var/dat = {"
<a href='?src=[REF(src)];[HrefToken()];makeAntag=traitors'>Make Traitors</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=changelings'>Make Changelings</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=revs'>Make Revs</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=cult'>Make Cult</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=clockcult'>Make Clockwork Cult</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=blob'>Make Blob</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=wizard'>Make Wizard (Requires Ghosts)</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=nukeops'>Make Nuke Team (Requires Ghosts)</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=centcom'>Make CentCom Response Team (Requires Ghosts)</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=abductors'>Make Abductor Team (Requires Ghosts)</a><br>
<a href='?src=[REF(src)];[HrefToken()];makeAntag=revenant'>Make Revenant (Requires Ghost)</a><br>
"}
var/datum/browser/popup = new(usr, "oneclickantag", "Quick-Create Antagonist", 400, 400)
popup.set_content(dat)
popup.open()
/datum/admins/proc/isReadytoRumble(mob/living/carbon/human/applicant, targetrole, onstation = TRUE, conscious = TRUE)
if(applicant.mind.special_role)
return FALSE
if(!(targetrole in applicant.client.prefs.be_special))
return FALSE
if(onstation)
var/turf/T = get_turf(applicant)
if(!is_station_level(T.z))
return FALSE
if(conscious && applicant.stat) //incase you don't care about a certain antag being unconcious when made, ie if they have selfhealing abilities.
return FALSE
if(!considered_alive(applicant.mind) || considered_afk(applicant.mind)) //makes sure the player isn't a zombie, brain, or just afk all together
return FALSE
return (!jobban_isbanned(applicant, targetrole) && !jobban_isbanned(applicant, ROLE_SYNDICATE))
/datum/admins/proc/makeTraitors()
var/datum/game_mode/traitor/temp = new
if(CONFIG_GET(flag/protect_roles_from_antagonist))
temp.restricted_jobs += temp.protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
temp.restricted_jobs += "Assistant"
var/list/mob/living/carbon/human/candidates = list()
var/mob/living/carbon/human/H = null
for(var/mob/living/carbon/human/applicant in GLOB.player_list)
if(isReadytoRumble(applicant, ROLE_TRAITOR))
if(temp.age_check(applicant.client))
if(!(applicant.job in temp.restricted_jobs))
candidates += applicant
if(candidates.len)
var/numTraitors = min(candidates.len, 3)
for(var/i = 0, i<numTraitors, i++)
H = pick(candidates)
H.mind.make_Traitor()
candidates.Remove(H)
return 1
return 0
/datum/admins/proc/makeChangelings()
var/datum/game_mode/changeling/temp = new
if(CONFIG_GET(flag/protect_roles_from_antagonist))
temp.restricted_jobs += temp.protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
temp.restricted_jobs += "Assistant"
var/list/mob/living/carbon/human/candidates = list()
var/mob/living/carbon/human/H = null
for(var/mob/living/carbon/human/applicant in GLOB.player_list)
if(isReadytoRumble(applicant, ROLE_CHANGELING))
if(temp.age_check(applicant.client))
if(!(applicant.job in temp.restricted_jobs))
candidates += applicant
if(candidates.len)
var/numChangelings = min(candidates.len, 3)
for(var/i = 0, i<numChangelings, i++)
H = pick(candidates)
H.mind.make_Changeling()
candidates.Remove(H)
return 1
return 0
/datum/admins/proc/makeRevs()
var/datum/game_mode/revolution/temp = new
if(CONFIG_GET(flag/protect_roles_from_antagonist))
temp.restricted_jobs += temp.protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
temp.restricted_jobs += "Assistant"
var/list/mob/living/carbon/human/candidates = list()
var/mob/living/carbon/human/H = null
for(var/mob/living/carbon/human/applicant in GLOB.player_list)
if(isReadytoRumble(applicant, ROLE_REV))
if(temp.age_check(applicant.client))
if(!(applicant.job in temp.restricted_jobs))
candidates += applicant
if(candidates.len)
var/numRevs = min(candidates.len, 3)
for(var/i = 0, i<numRevs, i++)
H = pick(candidates)
H.mind.make_Rev()
candidates.Remove(H)
return 1
return 0
/datum/admins/proc/makeWizard()
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you wish to be considered for the position of a Wizard Foundation 'diplomat'?", ROLE_WIZARD, null)
var/mob/dead/observer/selected = pick_n_take(candidates)
var/mob/living/carbon/human/new_character = makeBody(selected)
new_character.mind.make_Wizard()
return TRUE
/datum/admins/proc/makeCult()
var/datum/game_mode/cult/temp = new
if(CONFIG_GET(flag/protect_roles_from_antagonist))
temp.restricted_jobs += temp.protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
temp.restricted_jobs += "Assistant"
var/list/mob/living/carbon/human/candidates = list()
var/mob/living/carbon/human/H = null
for(var/mob/living/carbon/human/applicant in GLOB.player_list)
if(isReadytoRumble(applicant, ROLE_CULTIST))
if(temp.age_check(applicant.client))
if(!(applicant.job in temp.restricted_jobs))
candidates += applicant
if(candidates.len)
var/numCultists = min(candidates.len, 4)
for(var/i = 0, i<numCultists, i++)
H = pick(candidates)
H.mind.make_Cultist()
candidates.Remove(H)
return 1
return 0
/datum/admins/proc/makeClockCult()
var/datum/game_mode/clockwork_cult/temp = new
if(CONFIG_GET(flag/protect_roles_from_antagonist))
temp.restricted_jobs += temp.protected_jobs
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
temp.restricted_jobs += "Assistant"
var/list/mob/living/carbon/human/candidates = list()
var/mob/living/carbon/human/H = null
for(var/mob/living/carbon/human/applicant in GLOB.player_list)
if(isReadytoRumble(applicant, ROLE_SERVANT_OF_RATVAR))
if(temp.age_check(applicant.client))
if(!(applicant.job in temp.restricted_jobs))
candidates += applicant
if(candidates.len)
var/numCultists = min(candidates.len, 4)
for(var/i = 0, i<numCultists, i++)
H = pick(candidates)
to_chat(H, "<span class='heavy_brass'>The world before you suddenly glows a brilliant yellow. You hear the whooshing steam and clanking cogs of a billion billion machines, and all at once \
you see the truth. Ratvar, the Clockwork Justiciar, lies derelict and forgotten in an unseen realm, and he has selected you as one of his harbringers. You are now a servant of \
Ratvar, and you will bring him back.</span>")
H.playsound_local(get_turf(H), 'sound/ambience/antag/clockcultalr.ogg', 100, FALSE, pressure_affected = FALSE)
add_servant_of_ratvar(H, TRUE)
SSticker.mode.equip_servant(H)
candidates.Remove(H)
return 1
return 0
/datum/admins/proc/makeNukeTeam()
var/datum/game_mode/nuclear/temp = new
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you wish to be considered for a nuke team being sent in?", ROLE_OPERATIVE, temp)
var/list/mob/dead/observer/chosen = list()
var/mob/dead/observer/theghost = null
if(candidates.len)
var/numagents = 5
var/agentcount = 0
for(var/i = 0, i<numagents,i++)
shuffle_inplace(candidates) //More shuffles means more randoms
for(var/mob/j in candidates)
if(!j || !j.client)
candidates.Remove(j)
continue
theghost = j
candidates.Remove(theghost)
chosen += theghost
agentcount++
break
//Making sure we have atleast 3 Nuke agents, because less than that is kinda bad
if(agentcount < 3)
return 0
//Let's find the spawn locations
var/leader_chosen = FALSE
var/datum/team/nuclear/nuke_team
for(var/mob/c in chosen)
var/mob/living/carbon/human/new_character=makeBody(c)
if(!leader_chosen)
leader_chosen = TRUE
var/datum/antagonist/nukeop/N = new_character.mind.add_antag_datum(/datum/antagonist/nukeop/leader)
nuke_team = N.nuke_team
else
new_character.mind.add_antag_datum(/datum/antagonist/nukeop,nuke_team)
return 1
else
return 0
/datum/admins/proc/makeAliens()
var/datum/round_event/ghost_role/alien_infestation/E = new(FALSE)
E.spawncount = 3
// TODO The fact we have to do this rather than just have events start
// when we ask them to, is bad.
E.processing = TRUE
return TRUE
/datum/admins/proc/makeSpaceNinja()
new /datum/round_event/ghost_role/ninja()
return 1
// DEATH SQUADS
/datum/admins/proc/makeDeathsquad()
return makeEmergencyresponseteam(/datum/ert/deathsquad)
// CENTCOM RESPONSE TEAM
/datum/admins/proc/makeERTTemplateModified(list/settings)
. = settings
var/datum/ert/newtemplate = settings["mainsettings"]["template"]["value"]
if (isnull(newtemplate))
return
if (!ispath(newtemplate))
newtemplate = text2path(newtemplate)
newtemplate = new newtemplate
.["mainsettings"]["teamsize"]["value"] = newtemplate.teamsize
.["mainsettings"]["mission"]["value"] = newtemplate.mission
.["mainsettings"]["polldesc"]["value"] = newtemplate.polldesc
.["mainsettings"]["open_armory"]["value"] = newtemplate.opendoors ? "Yes" : "No"
/datum/admins/proc/equipAntagOnDummy(mob/living/carbon/human/dummy/mannequin, datum/antagonist/antag)
for(var/I in mannequin.get_equipped_items(TRUE))
qdel(I)
if (ispath(antag, /datum/antagonist/ert))
var/datum/antagonist/ert/ert = antag
mannequin.equipOutfit(initial(ert.outfit), TRUE)
else if (ispath(antag, /datum/antagonist/official))
mannequin.equipOutfit(/datum/outfit/centcom_official, TRUE)
/datum/admins/proc/makeERTPreviewIcon(list/settings)
// Set up the dummy for its photoshoot
var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_ADMIN)
var/prefs = settings["mainsettings"]
var/datum/ert/template = prefs["template"]["value"]
if (isnull(template))
return null
if (!ispath(template))
template = text2path(prefs["template"]["value"]) // new text2path ... doesn't compile in 511
template = new template
var/datum/antagonist/ert/ert = template.leader_role
equipAntagOnDummy(mannequin, ert)
COMPILE_OVERLAYS(mannequin)
CHECK_TICK
var/icon/preview_icon = icon('icons/effects/effects.dmi', "nothing")
preview_icon.Scale(48+32, 16+32)
CHECK_TICK
mannequin.setDir(NORTH)
var/icon/stamp = getFlatIcon(mannequin)
CHECK_TICK
preview_icon.Blend(stamp, ICON_OVERLAY, 25, 17)
CHECK_TICK
mannequin.setDir(WEST)
stamp = getFlatIcon(mannequin)
CHECK_TICK
preview_icon.Blend(stamp, ICON_OVERLAY, 1, 9)
CHECK_TICK
mannequin.setDir(SOUTH)
stamp = getFlatIcon(mannequin)
CHECK_TICK
preview_icon.Blend(stamp, ICON_OVERLAY, 49, 1)
CHECK_TICK
preview_icon.Scale(preview_icon.Width() * 2, preview_icon.Height() * 2) // Scaling here to prevent blurring in the browser.
CHECK_TICK
unset_busy_human_dummy(DUMMY_HUMAN_SLOT_ADMIN)
return preview_icon
/datum/admins/proc/makeEmergencyresponseteam(var/datum/ert/ertemplate = null)
if (ertemplate)
ertemplate = new ertemplate
else
ertemplate = new /datum/ert/centcom_official
var/list/settings = list(
"preview_callback" = CALLBACK(src, .proc/makeERTPreviewIcon),
"mainsettings" = list(
"template" = list("desc" = "Template", "callback" = CALLBACK(src, .proc/makeERTTemplateModified), "type" = "datum", "path" = "/datum/ert", "subtypesonly" = TRUE, "value" = ertemplate.type),
"teamsize" = list("desc" = "Team Size", "type" = "number", "value" = ertemplate.teamsize),
"mission" = list("desc" = "Mission", "type" = "string", "value" = ertemplate.mission),
"polldesc" = list("desc" = "Ghost poll description", "type" = "string", "value" = ertemplate.polldesc),
"enforce_human" = list("desc" = "Enforce human authority", "type" = "boolean", "value" = "[(CONFIG_GET(flag/enforce_human_authority) ? "Yes" : "No")]"),
"open_armory" = list("desc" = "Open armory doors", "type" = "boolean", "value" = "[(ertemplate.opendoors ? "Yes" : "No")]"),
)
)
var/list/prefreturn = presentpreflikepicker(usr,"Customize ERT", "Customize ERT", Button1="Ok", width = 600, StealFocus = 1,Timeout = 0, settings=settings)
if (isnull(prefreturn))
return FALSE
if (prefreturn["button"] == 1)
var/list/prefs = settings["mainsettings"]
var/templtype = prefs["template"]["value"]
if (!ispath(prefs["template"]["value"]))
templtype = text2path(prefs["template"]["value"]) // new text2path ... doesn't compile in 511
if (ertemplate.type != templtype)
ertemplate = new templtype
ertemplate.teamsize = prefs["teamsize"]["value"]
ertemplate.mission = prefs["mission"]["value"]
ertemplate.polldesc = prefs["polldesc"]["value"]
ertemplate.enforce_human = prefs["enforce_human"]["value"] == "Yes" ? TRUE : FALSE
ertemplate.opendoors = prefs["open_armory"]["value"] == "Yes" ? TRUE : FALSE
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you wish to be considered for [ertemplate.polldesc] ?", "deathsquad", null)
var/teamSpawned = FALSE
if(candidates.len > 0)
//Pick the (un)lucky players
var/numagents = min(ertemplate.teamsize,candidates.len)
//Create team
var/datum/team/ert/ert_team = new ertemplate.team
if(ertemplate.rename_team)
ert_team.name = ertemplate.rename_team
//Asign team objective
var/datum/objective/missionobj = new
missionobj.team = ert_team
missionobj.explanation_text = ertemplate.mission
missionobj.completed = TRUE
ert_team.objectives += missionobj
ert_team.mission = missionobj
var/list/spawnpoints = GLOB.emergencyresponseteamspawn
while(numagents && candidates.len)
if (numagents > spawnpoints.len)
numagents--
continue // This guy's unlucky, not enough spawn points, we skip him.
var/spawnloc = spawnpoints[numagents]
var/mob/dead/observer/chosen_candidate = pick(candidates)
candidates -= chosen_candidate
if(!chosen_candidate.key)
continue
//Spawn the body
var/mob/living/carbon/human/ERTOperative = new ertemplate.mobtype(spawnloc)
chosen_candidate.client.prefs.copy_to(ERTOperative)
ERTOperative.key = chosen_candidate.key
if(ertemplate.enforce_human || ERTOperative.dna.species.dangerous_existence) // Don't want any exploding plasmemes
ERTOperative.set_species(/datum/species/human)
//Give antag datum
var/datum/antagonist/ert/ert_antag
if(numagents == 1)
ert_antag = new ertemplate.leader_role
else
ert_antag = ertemplate.roles[WRAP(numagents,1,length(ertemplate.roles) + 1)]
ert_antag = new ert_antag
ERTOperative.mind.add_antag_datum(ert_antag,ert_team)
ERTOperative.mind.assigned_role = ert_antag.name
//Logging and cleanup
log_game("[key_name(ERTOperative)] has been selected as an [ert_antag.name]")
numagents--
teamSpawned++
if (teamSpawned)
message_admins("[ertemplate.polldesc] has spawned with the mission: [ertemplate.mission]")
//Open the Armory doors
if(ertemplate.opendoors)
for(var/obj/machinery/door/poddoor/ert/door in GLOB.airlocks)
door.open()
CHECK_TICK
return TRUE
else
return FALSE
return
//Abductors
/datum/admins/proc/makeAbductorTeam()
new /datum/round_event/ghost_role/abductor
return 1
/datum/admins/proc/makeRevenant()
new /datum/round_event/ghost_role/revenant(TRUE, TRUE)
return 1
+31
View File
@@ -0,0 +1,31 @@
GLOBAL_VAR_INIT(highlander, FALSE)
/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle.
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
return
GLOB.highlander = TRUE
send_to_playing_players("<span class='boldannounce'><font size=6>THERE CAN BE ONLY ONE</font></span>")
for(var/obj/item/disk/nuclear/N in GLOB.poi_list)
var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving)
if (component)
component.relocate() //Gets it out of bags and such
for(var/mob/living/carbon/human/H in GLOB.player_list)
if(H.stat == DEAD || !(H.client))
continue
H.make_scottish()
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used THERE CAN BE ONLY ONE!</span>")
log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50)
/client/proc/only_one_delayed()
send_to_playing_players("<span class='userdanger'>Bagpipes begin to blare. You feel Scottish pride coming over you.</span>")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!</span>")
log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(src, .proc/only_one), 420)
/mob/living/carbon/human/proc/make_scottish()
mind.add_antag_datum(/datum/antagonist/highlander)
+42
View File
@@ -0,0 +1,42 @@
/client/proc/panicbunker()
set category = "Server"
set name = "Toggle Panic Bunker"
if (!CONFIG_GET(flag/sql_enabled))
to_chat(usr, "<span class='adminnotice'>The Database is not enabled!</span>")
return
var/new_pb = !CONFIG_GET(flag/panic_bunker)
CONFIG_SET(flag/panic_bunker, new_pb)
log_admin("[key_name(usr)] has toggled the Panic Bunker, it is now [new_pb ? "on" : "off"]")
message_admins("[key_name_admin(usr)] has toggled the Panic Bunker, it is now [new_pb ? "enabled" : "disabled"].")
if (new_pb && !SSdbcore.Connect())
message_admins("The Database is not connected! Panic bunker will not work until the connection is reestablished.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Panic Bunker", "[new_pb ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
send2irc("Panic Bunker", "[key_name(usr)] has toggled the Panic Bunker, it is now [new_pb ? "enabled" : "disabled"].")
/client/proc/addbunkerbypass(ckeytobypass as text)
set category = "Special Verbs"
set name = "Add PB Bypass"
set desc = "Allows a given ckey to connect despite the panic bunker for a given round."
if(!CONFIG_GET(flag/sql_enabled))
to_chat(usr, "<span class='adminnotice'>The Database is not enabled!</span>")
return
GLOB.bunker_passthrough |= ckey(ckeytobypass)
log_admin("[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.")
message_admins("[key_name_admin(usr)] has added [ckeytobypass] to the current round's bunker bypass list.")
send2irc("Panic Bunker", "[key_name(usr)] has added [ckeytobypass] to the current round's bunker bypass list.")
/client/proc/revokebunkerbypass(ckeytobypass as text)
set category = "Special Verbs"
set name = "Revoke PB Bypass"
set desc = "Revoke's a ckey's permission to bypass the panic bunker for a given round."
if(!CONFIG_GET(flag/sql_enabled))
to_chat(usr, "<span class='adminnotice'>The Database is not enabled!</span>")
return
GLOB.bunker_passthrough -= ckey(ckeytobypass)
log_admin("[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.")
message_admins("[key_name_admin(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.")
send2irc("Panic Bunker", "[key_name(usr)] has removed [ckeytobypass] from the current round's bunker bypass list.")
+203
View File
@@ -0,0 +1,203 @@
/client/proc/play_sound(S as sound)
set category = "Fun"
set name = "Play Global Sound"
if(!check_rights(R_SOUNDS))
return
var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num
if(!vol)
return
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
freq = 1
vol = CLAMP(vol, 1, 100)
var/sound/admin_sound = new()
admin_sound.file = S
admin_sound.priority = 250
admin_sound.channel = CHANNEL_ADMIN
admin_sound.frequency = freq
admin_sound.wait = 1
admin_sound.repeat = 0
admin_sound.status = SOUND_STREAM
admin_sound.volume = vol
var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel")
switch(res)
if("Yes")
to_chat(world, "<span class='boldannounce'>An admin played: [S]</span>")
if("Cancel")
return
log_admin("[key_name(src)] played sound [S]")
message_admins("[key_name_admin(src)] played sound [S]")
for(var/mob/M in GLOB.player_list)
if(M.client.prefs.toggles & SOUND_MIDI)
var/user_vol = M.client.chatOutput.adminMusicVolume
if(user_vol)
admin_sound.volume = vol * (user_vol / 100)
SEND_SOUND(M, admin_sound)
admin_sound.volume = vol
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_local_sound(S as sound)
set category = "Fun"
set name = "Play Local Sound"
if(!check_rights(R_SOUNDS))
return
log_admin("[key_name(src)] played a local sound [S]")
message_admins("[key_name_admin(src)] played a local sound [S]")
playsound(get_turf(src.mob), S, 50, 0, 0)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_web_sound()
set category = "Fun"
set name = "Play Internet Sound"
if(!check_rights(R_SOUNDS))
return
var/ytdl = CONFIG_GET(string/invoke_youtubedl)
if(!ytdl)
to_chat(src, "<span class='boldwarning'>Youtube-dl was not configured, action unavailable</span>") //Check config.txt for the INVOKE_YOUTUBEDL value
return
var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null
if(istext(web_sound_input))
var/web_sound_url = ""
var/stop_web_sounds = FALSE
var/pitch
if(length(web_sound_input))
web_sound_input = trim(web_sound_input)
if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol))
to_chat(src, "<span class='boldwarning'>Non-http(s) URIs are not allowed.</span>")
to_chat(src, "<span class='warning'>For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.</span>")
return
var/shell_scrubbed_input = shell_url_scrub(web_sound_input)
var/list/output = world.shelleo("[ytdl] --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"")
var/errorlevel = output[SHELLEO_ERRORLEVEL]
var/stdout = output[SHELLEO_STDOUT]
var/stderr = output[SHELLEO_STDERR]
if(!errorlevel)
var/list/data
try
data = json_decode(stdout)
catch(var/exception/e)
to_chat(src, "<span class='boldwarning'>Youtube-dl JSON parsing FAILED:</span>")
to_chat(src, "<span class='warning'>[e]: [stdout]</span>")
return
if (data["url"])
web_sound_url = data["url"]
var/title = "[data["title"]]"
var/webpage_url = title
if (data["webpage_url"])
webpage_url = "<a href=\"[data["webpage_url"]]\">[title]</a>"
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
freq = 1
pitch = freq
var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel")
switch(res)
if("Yes")
to_chat(world, "<span class='boldannounce'>An admin played: [webpage_url]</span>")
if("Cancel")
return
SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]"))
log_admin("[key_name(src)] played web sound: [web_sound_input]")
message_admins("[key_name(src)] played web sound: [web_sound_input]")
else
to_chat(src, "<span class='boldwarning'>Youtube-dl URL retrieval FAILED:</span>")
to_chat(src, "<span class='warning'>[stderr]</span>")
else //pressed ok with blank
log_admin("[key_name(src)] stopped web sound")
message_admins("[key_name(src)] stopped web sound")
web_sound_url = null
stop_web_sounds = TRUE
if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol))
to_chat(src, "<span class='boldwarning'>BLOCKED: Content URL not using http(s) protocol</span>")
to_chat(src, "<span class='warning'>The media provider returned a content URL that isn't using the HTTP or HTTPS protocol</span>")
return
if(web_sound_url || stop_web_sounds)
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
if(!stop_web_sounds)
C.chatOutput.sendMusic(web_sound_url, pitch)
else
C.chatOutput.stopMusic()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound")
/client/proc/set_round_end_sound(S as sound)
set category = "Fun"
set name = "Set Round End Sound"
if(!check_rights(R_SOUNDS))
return
SSticker.SetRoundEndSound(S)
log_admin("[key_name(src)] set the round end sound to [S]")
message_admins("[key_name_admin(src)] set the round end sound to [S]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_web_sound_manual()
set category = "Fun"
set name = "Manual Play Internet Sound"
if(!check_rights(R_SOUNDS))
return
var/web_sound_input = input("Enter youtube-dl fetched content URL (supported sites only, leave blank to stop playing)", "Send youtube-dl media link") as text|null
if(!istext(web_sound_input))
return
web_sound_input = trim(web_sound_input)
if(!length(web_sound_input))
log_admin("[key_name(src)] stopped web sound")
message_admins("[key_name(src)] stopped web sound")
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.stopMusic()
return
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
return
if(web_sound_input && !findtext(web_sound_input, GLOB.is_http_protocol))
to_chat(src, "<span class='boldwarning'>BLOCKED: Content URL not using http(s) protocol</span>")
to_chat(src, "<span class='warning'>The media provider returned a content URL that isn't using the HTTP or HTTPS protocol</span>")
return
SSblackbox.record_feedback("nested tally", "played_url_manual", 1, list("[ckey]", "[web_sound_input]"))
log_admin("[key_name(src)] manually played web sound: [web_sound_input]")
message_admins("[key_name(src)] manually played web sound: <a href='web_sound_input'>HREF</a>")
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.sendMusic(web_sound_input, freq)
/client/proc/stop_sounds()
set category = "Debug"
set name = "Stop All Playing Sounds"
if(!src.holder)
return
log_admin("[key_name(src)] stopped all currently playing sounds.")
message_admins("[key_name_admin(src)] stopped all currently playing sounds.")
for(var/mob/M in GLOB.player_list)
if(M.client)
SEND_SOUND(M, sound(null))
var/client/C = M.client
if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.stopMusic()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop All Playing Sounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+53
View File
@@ -0,0 +1,53 @@
/proc/possess(obj/O in world)
set name = "Possess Obj"
set category = "Object"
if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession))
to_chat(usr, "[O] is too powerful for you to possess.")
return
var/turf/T = get_turf(O)
if(T)
log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]")
message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]")
else
log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location")
message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location")
if(!usr.control_object) //If you're not already possessing something...
usr.name_archive = usr.real_name
usr.loc = O
usr.real_name = O.name
usr.name = O.name
usr.reset_perspective(O)
usr.control_object = O
SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/release()
set name = "Release Obj"
set category = "Object"
//usr.loc = get_turf(usr)
if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object
usr.real_name = usr.name_archive
usr.name_archive = ""
usr.name = usr.real_name
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.name = H.get_visible_name()
usr.loc = get_turf(usr.control_object)
usr.reset_perspective()
usr.control_object = null
SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/givetestverbs(mob/M in GLOB.mob_list)
set desc = "Give this guy possess/release verbs"
set category = "Debug"
set name = "Give Possessing Verbs"
M.verbs += /proc/possess
M.verbs += /proc/release
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+75
View File
@@ -0,0 +1,75 @@
/mob/verb/pray(msg as text)
set category = "IC"
set name = "Pray"
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
return
log_prayer("[src.key]/([src.name]): [msg]")
if(usr.client)
if(usr.client.prefs.muted & MUTE_PRAY)
to_chat(usr, "<span class='danger'>You cannot pray (muted).</span>")
return
if(src.client.handle_spam_prevention(msg,MUTE_PRAY))
return
var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible")
var/font_color = "purple"
var/prayer_type = "PRAYER"
var/deity
if(usr.job == "Chaplain")
cross.icon_state = "kingyellow"
font_color = "blue"
prayer_type = "CHAPLAIN PRAYER"
if(GLOB.deity)
deity = GLOB.deity
else if(iscultist(usr))
cross.icon_state = "tome"
font_color = "red"
prayer_type = "CULTIST PRAYER"
deity = "Nar'Sie"
else if(isliving(usr))
var/mob/living/L = usr
if(HAS_TRAIT(L, TRAIT_SPIRITUAL))
cross.icon_state = "holylight"
font_color = "blue"
prayer_type = "SPIRITUAL PRAYER"
var/msg_tmp = msg
msg = "<span class='adminnotice'>[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> <span class='linkify'>[msg]</span></span>"
for(var/client/C in GLOB.admins)
if(C.prefs.chat_toggles & CHAT_PRAYER)
to_chat(C, msg)
if(C.prefs.toggles & SOUND_PRAYERS)
if(usr.job == "Chaplain")
SEND_SOUND(C, sound('sound/effects/pray.ogg'))
to_chat(usr, "<span class='info'>You pray to the gods: \"[msg_tmp]\"</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//log_admin("HELP: [key_name(src)]: [msg]")
/proc/CentCom_announce(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>CENTCOM:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/proc/Syndicate_announce(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=crimson>SYNDICATE:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/proc/Nuke_request(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>NUKE CODE REQUEST:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
/client/proc/reestablish_db_connection()
set category = "Special Verbs"
set name = "Reestablish DB Connection"
if (!CONFIG_GET(flag/sql_enabled))
to_chat(usr, "<span class='adminnotice'>The Database is not enabled!</span>")
return
if (SSdbcore.IsConnected())
if (!check_rights(R_DEBUG,0))
alert("The database is already connected! (Only those with +debug can force a reconnection)", "The database is already connected!")
return
var/reconnect = alert("The database is already connected! If you *KNOW* that this is incorrect, you can force a reconnection", "The database is already connected!", "Force Reconnect", "Cancel")
if (reconnect != "Force Reconnect")
return
SSdbcore.Disconnect()
log_admin("[key_name(usr)] has forced the database to disconnect")
message_admins("[key_name_admin(usr)] has <b>forced</b> the database to disconnect!")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Force Reestablished Database Connection") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
log_admin("[key_name(usr)] is attempting to re-established the DB Connection")
message_admins("[key_name_admin(usr)] is attempting to re-established the DB Connection")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reestablished Database Connection") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
SSdbcore.failed_connections = 0
if(!SSdbcore.Connect())
message_admins("Database connection failed: " + SSdbcore.ErrorMsg())
else
message_admins("Database connection re-established")
+70
View File
@@ -0,0 +1,70 @@
/datum/admins/proc/spawn_objasmob(object as text)
set category = "Debug"
set desc = "(obj path) Spawn object-mob"
set name = "Spawn object-mob"
if(!check_rights(R_SPAWN))
return
var/chosen = pick_closest_path(object, make_types_fancy(subtypesof(/obj)))
if (!chosen)
return
var/mob/living/simple_animal/hostile/mimic/copy/basemob = /mob/living/simple_animal/hostile/mimic/copy
var/obj/chosen_obj = text2path(chosen)
var/list/settings = list(
"mainsettings" = list(
"name" = list("desc" = "Name", "type" = "string", "value" = "Bob"),
"maxhealth" = list("desc" = "Max. health", "type" = "number", "value" = 100),
"access" = list("desc" = "Access ID", "type" = "datum", "path" = "/obj/item/card/id", "value" = "Default"),
"objtype" = list("desc" = "Base obj type", "type" = "datum", "path" = "/obj", "value" = "[chosen]"),
"googlyeyes" = list("desc" = "Googly eyes", "type" = "boolean", "value" = "No"),
"disableai" = list("desc" = "Disable AI", "type" = "boolean", "value" = "Yes"),
"idledamage" = list("desc" = "Damaged while idle", "type" = "boolean", "value" = "No"),
"dropitem" = list("desc" = "Drop obj on death", "type" = "boolean", "value" = "Yes"),
"mobtype" = list("desc" = "Base mob type", "type" = "datum", "path" = "/mob/living/simple_animal/hostile/mimic/copy", "value" = "/mob/living/simple_animal/hostile/mimic/copy"),
"ckey" = list("desc" = "ckey", "type" = "ckey", "value" = "none"),
)
)
var/list/prefreturn = presentpreflikepicker(usr,"Customize mob", "Customize mob", Button1="Ok", width = 450, StealFocus = 1,Timeout = 0, settings=settings)
if (prefreturn["button"] == 1)
settings = prefreturn["settings"]
var/mainsettings = settings["mainsettings"]
chosen_obj = text2path(mainsettings["objtype"]["value"])
basemob = text2path(mainsettings["mobtype"]["value"])
if (!ispath(basemob, /mob/living/simple_animal/hostile/mimic/copy) || !ispath(chosen_obj, /obj))
to_chat(usr, "Mob or object path invalid")
basemob = new basemob(get_turf(usr), new chosen_obj(get_turf(usr)), usr, mainsettings["dropitem"]["value"] == "Yes" ? FALSE : TRUE, (mainsettings["googlyeyes"]["value"] == "Yes" ? FALSE : TRUE))
if (mainsettings["disableai"]["value"] == "Yes")
basemob.toggle_ai(AI_OFF)
if (mainsettings["idledamage"]["value"] == "No")
basemob.idledamage = FALSE
if (mainsettings["access"])
var/newaccess = text2path(mainsettings["access"]["value"])
if (ispath(newaccess))
basemob.access_card = new newaccess
if (mainsettings["maxhealth"]["value"])
if (!isnum(mainsettings["maxhealth"]["value"]))
mainsettings["maxhealth"]["value"] = text2num(mainsettings["maxhealth"]["value"])
if (mainsettings["maxhealth"]["value"] > 0)
basemob.maxHealth = basemob.maxHealth = mainsettings["maxhealth"]["value"]
if (mainsettings["name"]["value"])
basemob.name = basemob.real_name = html_decode(mainsettings["name"]["value"])
if (mainsettings["ckey"]["value"] != "none")
basemob.ckey = mainsettings["ckey"]["value"]
log_admin("[key_name(usr)] spawned a sentient object-mob [basemob] from [chosen_obj] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn object-mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+20
View File
@@ -0,0 +1,20 @@
/client/proc/triple_ai()
set category = "Fun"
set name = "Create AI Triumvirate"
if(SSticker.current_state > GAME_STATE_PREGAME)
to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.")
return
var/datum/job/job = SSjob.GetJob("AI")
if(!job)
to_chat(usr, "Unable to locate the AI job")
return
if(SSticker.triai)
SSticker.triai = 0
to_chat(usr, "Only one AI will be spawned at round start.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has toggled off triple AIs at round start.</span>")
else
SSticker.triai = 1
to_chat(usr, "There will be an AI Triumvirate at round start.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has toggled on triple AIs at round start.</span>")
+23
View File
@@ -0,0 +1,23 @@
#define WHITELISTFILE "[global.config.directory]/whitelist.txt"
GLOBAL_LIST(whitelist)
GLOBAL_PROTECT(whitelist)
/proc/load_whitelist()
GLOB.whitelist = list()
for(var/line in world.file2list(WHITELISTFILE))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
GLOB.whitelist += ckey(line)
if(!GLOB.whitelist.len)
GLOB.whitelist = null
/proc/check_whitelist(var/ckey)
if(!GLOB.whitelist)
return FALSE
. = (ckey in GLOB.whitelist)
#undef WHITELISTFILE
@@ -0,0 +1,249 @@
GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist
var/name = "Antagonist"
var/roundend_category = "other antagonists" //Section of roundend report, datums with same category will be displayed together, also default header for the section
var/show_in_roundend = TRUE //Set to false to hide the antagonists from roundend report
var/datum/mind/owner //Mind that owns this datum
var/silent = FALSE //Silent will prevent the gain/lose texts to show
var/can_coexist_with_others = TRUE //Whether or not the person will be able to have more than one datum
var/list/typecache_datum_blacklist = list() //List of datums this type can't coexist with
var/delete_on_mind_deletion = TRUE
var/job_rank
var/replace_banned = TRUE //Should replace jobbaned player with ghosts if granted.
var/list/objectives = list()
var/antag_memory = ""//These will be removed with antag datum
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens.
//Antag panel properties
var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
var/antagpanel_category = "Uncategorized" //Antagpanel will display these together, REQUIRED
var/show_name_in_check_antagonists = FALSE //Will append antagonist name in admin listings - use for categories that share more than one antag type
/datum/antagonist/New()
GLOB.antagonists += src
typecache_datum_blacklist = typecacheof(typecache_datum_blacklist)
/datum/antagonist/Destroy()
GLOB.antagonists -= src
if(owner)
LAZYREMOVE(owner.antag_datums, src)
owner = null
return ..()
/datum/antagonist/proc/can_be_owned(datum/mind/new_owner)
. = TRUE
var/datum/mind/tested = new_owner || owner
if(tested.has_antag_datum(type))
return FALSE
for(var/i in tested.antag_datums)
var/datum/antagonist/A = i
if(is_type_in_typecache(src, A.typecache_datum_blacklist))
return FALSE
//This will be called in add_antag_datum before owner assignment.
//Should return antag datum without owner.
/datum/antagonist/proc/specialization(datum/mind/new_owner)
return src
/datum/antagonist/proc/on_body_transfer(mob/living/old_body, mob/living/new_body)
remove_innate_effects(old_body)
apply_innate_effects(new_body)
//This handles the application of antag huds/special abilities
/datum/antagonist/proc/apply_innate_effects(mob/living/mob_override)
return
//This handles the removal of antag huds/special abilities
/datum/antagonist/proc/remove_innate_effects(mob/living/mob_override)
return
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/proc/create_team(datum/team/team)
return
//Proc called when the datum is given to a mind.
/datum/antagonist/proc/on_gain()
if(owner && owner.current)
if(!silent)
greet()
apply_innate_effects()
give_antag_moodies()
if(is_banned(owner.current) && replace_banned)
replace_banned_player()
/datum/antagonist/proc/is_banned(mob/M)
if(!M)
return FALSE
. = (jobban_isbanned(M, ROLE_SYNDICATE) || QDELETED(M) || (job_rank && (jobban_isbanned(M,job_rank) || QDELETED(M))))
/datum/antagonist/proc/replace_banned_player()
set waitfor = FALSE
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [name]?", "[name]", null, job_rank, 50, owner.current)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
to_chat(owner, "Your mob has been taken over by a ghost! Appeal your job ban if you want to avoid this in the future!")
message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(owner.current)]) to replace a jobbaned player.")
owner.current.ghostize(0)
owner.current.key = C.key
/datum/antagonist/proc/on_removal()
remove_innate_effects()
clear_antag_moodies()
if(owner)
LAZYREMOVE(owner.antag_datums, src)
if(!silent && owner.current)
farewell()
owner.objectives -= objectives
var/datum/team/team = get_team()
if(team)
team.remove_member(owner)
qdel(src)
/datum/antagonist/proc/greet()
return
/datum/antagonist/proc/farewell()
return
/datum/antagonist/proc/give_antag_moodies()
if(!antag_moodlet)
return
SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "antag_moodlet", antag_moodlet)
/datum/antagonist/proc/clear_antag_moodies()
if(!antag_moodlet)
return
SEND_SIGNAL(owner.current, COMSIG_CLEAR_MOOD_EVENT, "antag_moodlet")
//Returns the team antagonist belongs to if any.
/datum/antagonist/proc/get_team()
return
//Individual roundend report
/datum/antagonist/proc/roundend_report()
var/list/report = list()
if(!owner)
CRASH("antagonist datum without owner")
report += printplayer(owner)
var/objectives_complete = TRUE
if(owner.objectives.len)
report += printobjectives(owner)
for(var/datum/objective/objective in owner.objectives)
if(!objective.check_completion())
objectives_complete = FALSE
break
if(owner.objectives.len == 0 || objectives_complete)
report += "<span class='greentext big'>The [name] was successful!</span>"
else
report += "<span class='redtext big'>The [name] has failed!</span>"
return report.Join("<br>")
//Displayed at the start of roundend_category section, default to roundend_category header
/datum/antagonist/proc/roundend_report_header()
return "<span class='header'>The [roundend_category] were:</span><br>"
//Displayed at the end of roundend_category section
/datum/antagonist/proc/roundend_report_footer()
return
//ADMIN TOOLS
//Called when using admin tools to give antag status
/datum/antagonist/proc/admin_add(datum/mind/new_owner,mob/admin)
message_admins("[key_name_admin(admin)] made [new_owner.current] into [name].")
log_admin("[key_name(admin)] made [new_owner.current] into [name].")
new_owner.add_antag_datum(src)
//Called when removing antagonist using admin tools
/datum/antagonist/proc/admin_remove(mob/user)
if(!user)
return
message_admins("[key_name_admin(user)] has removed [name] antagonist status from [owner.current].")
log_admin("[key_name(user)] has removed [name] antagonist status from [owner.current].")
on_removal()
//gamemode/proc/is_mode_antag(antagonist/A) => TRUE/FALSE
//Additional data to display in antagonist panel section
//nuke disk code, genome count, etc
/datum/antagonist/proc/antag_panel_data()
return ""
/datum/antagonist/proc/enabled_in_preferences(datum/mind/M)
if(job_rank)
if(M.current && M.current.client && (job_rank in M.current.client.prefs.be_special))
return TRUE
else
return FALSE
return TRUE
// List if ["Command"] = CALLBACK(), user will be appeneded to callback arguments on execution
/datum/antagonist/proc/get_admin_commands()
. = list()
/datum/antagonist/Topic(href,href_list)
if(!check_rights(R_ADMIN))
return
//Antag memory edit
if (href_list["memory_edit"])
edit_memory(usr)
owner.traitor_panel()
return
//Some commands might delete/modify this datum clearing or changing owner
var/datum/mind/persistent_owner = owner
var/commands = get_admin_commands()
for(var/admin_command in commands)
if(href_list["command"] == admin_command)
var/datum/callback/C = commands[admin_command]
C.Invoke(usr)
persistent_owner.traitor_panel()
return
/datum/antagonist/proc/edit_memory(mob/user)
var/new_memo = copytext(trim(input(user,"Write new memory", "Memory", antag_memory) as null|message),1,MAX_MESSAGE_LEN)
if (isnull(new_memo))
return
antag_memory = new_memo
//This datum will autofill the name with special_role
//Used as placeholder for minor antagonists, please create proper datums for these
/datum/antagonist/auto_custom
show_in_antagpanel = FALSE
antagpanel_category = "Other"
show_name_in_check_antagonists = TRUE
/datum/antagonist/auto_custom/on_gain()
..()
name = owner.special_role
//Add all objectives not already owned by other datums to this one.
var/list/already_registered_objectives = list()
for(var/datum/antagonist/A in owner.antag_datums)
if(A == src)
continue
else
already_registered_objectives |= A.objectives
objectives = owner.objectives - already_registered_objectives
//This one is created by admin tools for custom objectives
/datum/antagonist/custom
antagpanel_category = "Custom"
show_name_in_check_antagonists = TRUE //They're all different
/datum/antagonist/custom/admin_add(datum/mind/new_owner,mob/admin)
var/custom_name = stripped_input(admin, "Custom antagonist name:", "Custom antag", "Antagonist")
if(custom_name)
name = custom_name
else
return
..()
@@ -0,0 +1,19 @@
//Returns MINDS of the assigned antags of given type/subtypes
/proc/get_antag_minds(antag_type,specific = FALSE)
. = list()
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
continue
if(!antag_type || !specific && istype(A,antag_type) || specific && A.type == antag_type)
. += A.owner
//Get all teams [of type team_type]
/proc/get_all_teams(team_type)
. = list()
for(var/V in GLOB.antagonists)
var/datum/antagonist/A = V
if(!A.owner)
continue
var/datum/team/T = A.get_team()
if(!team_type || istype(T,team_type))
. |= T
@@ -0,0 +1,53 @@
/datum/atom_hud/antag
hud_icons = list(ANTAG_HUD)
var/self_visible = TRUE
/datum/atom_hud/antag/hidden
self_visible = FALSE
/datum/atom_hud/antag/proc/join_hud(mob/M)
//sees_hud should be set to 0 if the mob does not get to see it's own hud type.
if(!istype(M))
CRASH("join_hud(): [M] ([M.type]) is not a mob!")
if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged
M.mind.antag_hud.leave_hud(M)
add_to_hud(M)
if(self_visible)
add_hud_to(M)
M.mind.antag_hud = src
/datum/atom_hud/antag/proc/leave_hud(mob/M)
if(!M)
return
if(!istype(M))
CRASH("leave_hud(): [M] ([M.type]) is not a mob!")
remove_from_hud(M)
remove_hud_from(M)
if(M.mind)
M.mind.antag_hud = null
//GAME_MODE PROCS
//called to set a mob's antag icon state
/proc/set_antag_hud(mob/M, new_icon_state)
if(!istype(M))
CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!")
var/image/holder = M.hud_list[ANTAG_HUD]
if(holder)
holder.icon_state = new_icon_state
if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime
M.mind.antag_hud_icon_state = new_icon_state
//MIND PROCS
//these are called by mind.transfer_to()
/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud)
leave_all_antag_huds()
set_antag_hud(current, antag_hud_icon_state)
if(newhud)
newhud.join_hud(current)
/datum/mind/proc/leave_all_antag_huds()
for(var/datum/atom_hud/antag/hud in GLOB.huds)
if(hud.hudusers[current])
hud.leave_hud(current)
@@ -0,0 +1,274 @@
/obj/item/antag_spawner
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_TINY
var/used = FALSE
/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
return
/obj/item/antag_spawner/proc/equip_antag(mob/target)
return
///////////WIZARD
/obj/item/antag_spawner/contract
name = "contract"
desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid."
icon = 'icons/obj/wizard.dmi'
icon_state ="scroll2"
/obj/item/antag_spawner/contract/attack_self(mob/user)
user.set_machine(src)
var/dat
if(used)
dat = "<B>You have already summoned your apprentice.</B><BR>"
else
dat = "<B>Contract of Apprenticeship:</B><BR>"
dat += "<I>Using this contract, you may summon an apprentice to aid you on your mission.</I><BR>"
dat += "<I>If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.</I><BR>"
dat += "<B>Which school of magic is your apprentice studying?:</B><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_DESTRUCTION]'>Destruction</A><BR>"
dat += "<I>Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_BLUESPACE]'>Bluespace Manipulation</A><BR>"
dat += "<I>Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_HEALING]'>Healing</A><BR>"
dat += "<I>Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_ROBELESS]'>Robeless</A><BR>"
dat += "<I>Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.</I><BR>"
user << browse(dat, "window=radio")
onclose(user, "radio")
return
/obj/item/antag_spawner/contract/Topic(href, href_list)
..()
var/mob/living/carbon/human/H = usr
if(H.stat || H.restrained())
return
if(!ishuman(H))
return 1
if(loc == H || (in_range(src, H) && isturf(loc)))
H.set_machine(src)
if(href_list["school"])
if(used)
to_chat(H, "You already used this contract!")
return
var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src)
if(LAZYLEN(candidates))
if(QDELETED(src))
return
if(used)
to_chat(H, "You already used this contract!")
return
used = TRUE
var/mob/dead/observer/C = pick(candidates)
spawn_antag(C.client, get_turf(src), href_list["school"],H.mind)
else
to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")
/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user)
new /obj/effect/particle_effect/smoke(T)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/mind/app_mind = M.mind
var/datum/antagonist/wizard/apprentice/app = new()
app.master = user
app.school = kind
var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard)
if(master_wizard)
if(!master_wizard.wiz_team)
master_wizard.create_wiz_team()
app.wiz_team = master_wizard.wiz_team
master_wizard.wiz_team.add_member(app_mind)
app_mind.add_antag_datum(app)
//TODO Kill these if possible
app_mind.assigned_role = "Apprentice"
app_mind.special_role = "apprentice"
//
SEND_SOUND(M, sound('sound/effects/magic.ogg'))
///////////BORGS AND OPERATIVES
/obj/item/antag_spawner/nuke_ops
name = "syndicate operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
var/borg_to_spawn
/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user)
if(used)
to_chat(user, "<span class='warning'>[src] is out of power!</span>")
return FALSE
if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE))
to_chat(user, "<span class='danger'>AUTHENTICATION FAILURE. ACCESS DENIED.</span>")
return FALSE
if(!user.onSyndieBase())
to_chat(user, "<span class='warning'>[src] is out of range! It can only be used at your base!</span>")
return FALSE
return TRUE
/obj/item/antag_spawner/nuke_ops/attack_self(mob/user)
if(!(check_usability(user)))
return
to_chat(user, "<span class='notice'>You activate [src] and wait for confirmation.</span>")
var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE)
if(LAZYLEN(nuke_candidates))
if(QDELETED(src) || !check_usability(user))
return
used = TRUE
var/mob/dead/observer/G = pick(nuke_candidates)
spawn_antag(G.client, get_turf(src), "syndieborg", user.mind)
do_sparks(4, TRUE, src)
qdel(src)
else
to_chat(user, "<span class='warning'>Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.</span>")
/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/antagonist/nukeop/new_op = new()
new_op.send_to_spawnpoint = FALSE
new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(creator_op)
M.mind.add_antag_datum(new_op,creator_op.nuke_team)
M.mind.special_role = "Nuclear Operative"
//////CLOWN OP
/obj/item/antag_spawner/nuke_ops/clown
name = "clown operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field."
/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop()
new_op.send_to_spawnpoint = FALSE
new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE)
if(creator_op)
M.mind.add_antag_datum(new_op, creator_op.nuke_team)
M.mind.special_role = "Clown Operative"
//////SYNDICATE BORG
/obj/item/antag_spawner/nuke_ops/borg_tele
name = "syndicate cyborg teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
/obj/item/antag_spawner/nuke_ops/borg_tele/assault
name = "syndicate assault cyborg teleporter"
borg_to_spawn = "Assault"
/obj/item/antag_spawner/nuke_ops/borg_tele/medical
name = "syndicate medical teleporter"
borg_to_spawn = "Medical"
/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/silicon/robot/R
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(!creator_op)
return
switch(borg_to_spawn)
if("Medical")
R = new /mob/living/silicon/robot/modules/syndicate/medical(T)
else
R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default
var/brainfirstname = pick(GLOB.first_names_male)
if(prob(50))
brainfirstname = pick(GLOB.first_names_female)
var/brainopslastname = pick(GLOB.last_names)
if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops.
brainopslastname = creator_op.nuke_team.syndicate_name
var/brainopsname = "[brainfirstname] [brainopslastname]"
R.mmi.name = "Man-Machine Interface: [brainopsname]"
R.mmi.brain.name = "[brainopsname]'s brain"
R.mmi.brainmob.real_name = brainopsname
R.mmi.brainmob.name = brainopsname
R.real_name = R.name
R.key = C.key
var/datum/antagonist/nukeop/new_borg = new()
new_borg.send_to_spawnpoint = FALSE
R.mind.add_antag_datum(new_borg,creator_op.nuke_team)
R.mind.special_role = "Syndicate Cyborg"
///////////SLAUGHTER DEMON
/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game
name = "vial of blood"
desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures."
icon = 'icons/obj/wizard.dmi'
icon_state = "vial"
var/shatter_msg = "<span class='notice'>You shatter the bottle, no turning back now!</span>"
var/veil_msg = "<span class='warning'>You sense a dark presence lurking just beyond the veil...</span>"
var/mob/living/demon_type = /mob/living/simple_animal/slaughter
var/antag_type = /datum/antagonist/slaughter
/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user)
if(!is_station_level(user.z))
to_chat(user, "<span class='notice'>You should probably wait until you reach the station.</span>")
return
if(used)
return
var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src)
if(LAZYLEN(candidates))
if(used || QDELETED(src))
return
used = TRUE
var/mob/dead/observer/C = pick(candidates)
spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind)
to_chat(user, shatter_msg)
to_chat(user, veil_msg)
playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1)
qdel(src)
else
to_chat(user, "<span class='notice'>You can't seem to work up the nerve to shatter the bottle. Perhaps you should try again later.</span>")
/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T)
var/mob/living/simple_animal/slaughter/S = new demon_type(holder)
S.holder = holder
S.key = C.key
S.mind.assigned_role = S.name
S.mind.special_role = S.name
S.mind.add_antag_datum(antag_type)
to_chat(S, S.playstyle_string)
to_chat(S, "<B>You are currently not currently in the same plane of existence as the station. \
Ctrl+Click a blood pool to manifest.</B>")
/obj/item/antag_spawner/slaughter_demon/laughter
name = "vial of tickles"
desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures."
icon = 'icons/obj/wizard.dmi'
icon_state = "vial"
color = "#FF69B4" // HOT PINK
veil_msg = "<span class='warning'>You sense an adorable presence lurking just beyond the veil...</span>"
demon_type = /mob/living/simple_animal/slaughter/laughter
antag_type = /datum/antagonist/slaughter/laughter
@@ -0,0 +1,51 @@
//A barebones antagonist team.
/datum/team
var/list/datum/mind/members = list()
var/name = "team"
var/member_name = "member"
var/list/objectives = list() //common objectives, these won't be added or removed automatically, subtypes handle this, this is here for bookkeeping purposes.
/datum/team/New(starting_members)
. = ..()
if(starting_members)
if(islist(starting_members))
for(var/datum/mind/M in starting_members)
add_member(M)
else
add_member(starting_members)
/datum/team/proc/is_solo()
return members.len == 1
/datum/team/proc/add_member(datum/mind/new_member)
members |= new_member
/datum/team/proc/remove_member(datum/mind/member)
members -= member
//Display members/victory/failure/objectives for the team
/datum/team/proc/roundend_report()
var/list/report = list()
report += "<span class='header'>[name]:</span>"
report += "The [member_name]s were:"
report += printplayerlist(members)
if(objectives.len)
report += "<span class='header'>Team had following objectives:</span>"
var/win = TRUE
var/objective_count = 1
for(var/datum/objective/objective in objectives)
if(objective.check_completion())
report += "<B>Objective #[objective_count]</B>: [objective.explanation_text] <span class='greentext'><B>Success!</span>"
else
report += "<B>Objective #[objective_count]</B>: [objective.explanation_text] <span class='redtext'>Fail.</span>"
win = FALSE
objective_count++
if(win)
report += "<span class='greentext'>The [name] was successful!</span>"
else
report += "<span class='redtext'>The [name] have failed!</span>"
return "<div class='panel redborder'>[report.Join("<br>")]</div>"
@@ -0,0 +1,54 @@
/datum/objective/abductee
completed = 1
/datum/objective/abductee/random
/datum/objective/abductee/random/New()
explanation_text = pick(world.file2list("strings/abductee_objectives.txt"))
/datum/objective/abductee/steal
explanation_text = "Steal all"
/datum/objective/abductee/steal/New()
var/target = pick(list("pets","lights","monkeys","fruits","shoes","bars of soap", "weapons", "computers", "organs"))
explanation_text+=" [target]."
/datum/objective/abductee/paint
explanation_text = "The station is hideous. You must color it all"
/datum/objective/abductee/paint/New()
var/color = pick(list("red", "blue", "green", "yellow", "orange", "purple", "black", "in rainbows", "in blood"))
explanation_text+= " [color]!"
/datum/objective/abductee/speech
explanation_text = "Your brain is broken... you can only communicate in"
/datum/objective/abductee/speech/New()
var/style = pick(list("pantomime", "rhyme", "haiku", "extended metaphors", "riddles", "extremely literal terms", "sound effects", "military jargon", "three word sentences"))
explanation_text+= " [style]."
/datum/objective/abductee/capture
explanation_text = "Capture"
/datum/objective/abductee/capture/New()
var/list/jobs = SSjob.occupations.Copy()
for(var/X in jobs)
var/datum/job/J = X
if(J.current_positions < 1)
jobs -= J
if(jobs.len > 0)
var/datum/job/target = pick(jobs)
explanation_text += " a [target.title]."
else
explanation_text += " someone."
/datum/objective/abductee/calling/New()
var/mob/dead/D = pick(GLOB.dead_mob_list)
if(D)
explanation_text = "You know that [D] has perished. Hold a seance to call [D.p_them()] from the spirit realm."
/datum/objective/abductee/forbiddennumber
/datum/objective/abductee/forbiddennumber/New()
var/number = rand(2,10)
explanation_text = "Ignore anything in a set of [number], they don't exist."
@@ -0,0 +1,216 @@
#define ABDUCTOR_MAX_TEAMS 4
/datum/antagonist/abductor
name = "Abductor"
roundend_category = "abductors"
antagpanel_category = "Abductor"
job_rank = ROLE_ABDUCTOR
show_in_antagpanel = FALSE //should only show subtypes
var/datum/team/abductor_team/team
var/sub_role
var/outfit
var/landmark_type
var/greet_text
/datum/antagonist/abductor/agent
name = "Abductor Agent"
sub_role = "Agent"
outfit = /datum/outfit/abductor/agent
landmark_type = /obj/effect/landmark/abductor/agent
greet_text = "Use your stealth technology and equipment to incapacitate humans for your scientist to retrieve."
show_in_antagpanel = TRUE
/datum/antagonist/abductor/scientist
name = "Abductor Scientist"
sub_role = "Scientist"
outfit = /datum/outfit/abductor/scientist
landmark_type = /obj/effect/landmark/abductor/scientist
greet_text = "Use your stealth technology and equipment to incapacitate humans for your scientist to retrieve."
show_in_antagpanel = TRUE
/datum/antagonist/abductor/create_team(datum/team/abductor_team/new_team)
if(!new_team)
return
if(!istype(new_team))
stack_trace("Wrong team type passed to [type] initialization.")
team = new_team
/datum/antagonist/abductor/get_team()
return team
/datum/antagonist/abductor/on_gain()
owner.special_role = "[name] [sub_role]"
owner.assigned_role = "[name] [sub_role]"
owner.objectives += team.objectives
finalize_abductor()
return ..()
/datum/antagonist/abductor/on_removal()
owner.objectives -= team.objectives
if(owner.current)
to_chat(owner.current,"<span class='userdanger'>You are no longer the [owner.special_role]!</span>")
owner.special_role = null
return ..()
/datum/antagonist/abductor/greet()
to_chat(owner.current, "<span class='notice'>You are the [owner.special_role]!</span>")
to_chat(owner.current, "<span class='notice'>With the help of your teammate, kidnap and experiment on station crew members!</span>")
to_chat(owner.current, "<span class='notice'>[greet_text]</span>")
owner.announce_objectives()
/datum/antagonist/abductor/proc/finalize_abductor()
//Equip
var/mob/living/carbon/human/H = owner.current
H.set_species(/datum/species/abductor)
H.real_name = "[team.name] [sub_role]"
H.equipOutfit(outfit)
//Teleport to ship
for(var/obj/effect/landmark/abductor/LM in GLOB.landmarks_list)
if(istype(LM, landmark_type) && LM.team_number == team.team_number)
H.forceMove(LM.loc)
break
update_abductor_icons_added(owner,"abductor")
/datum/antagonist/abductor/scientist/finalize_abductor()
..()
var/mob/living/carbon/human/H = owner.current
var/datum/species/abductor/A = H.dna.species
A.scientist = TRUE
/datum/antagonist/abductor/admin_add(datum/mind/new_owner,mob/admin)
var/list/current_teams = list()
for(var/datum/team/abductor_team/T in get_all_teams(/datum/team/abductor_team))
current_teams[T.name] = T
var/choice = input(admin,"Add to which team ?") as null|anything in (current_teams + "new team")
if (choice == "new team")
team = new
else if(choice in current_teams)
team = current_teams[choice]
else
return
new_owner.add_antag_datum(src)
log_admin("[key_name(usr)] made [key_name(new_owner.current)] [name] on [choice]!")
message_admins("[key_name_admin(usr)] made [key_name_admin(new_owner.current)] [name] on [choice] !")
/datum/antagonist/abductor/get_admin_commands()
. = ..()
.["Equip"] = CALLBACK(src,.proc/admin_equip)
/datum/antagonist/abductor/proc/admin_equip(mob/admin)
if(!ishuman(owner.current))
to_chat(admin, "<span class='warning'>This only works on humans!</span>")
return
var/mob/living/carbon/human/H = owner.current
var/gear = alert(admin,"Agent or Scientist Gear","Gear","Agent","Scientist")
if(gear)
if(gear=="Agent")
H.equipOutfit(/datum/outfit/abductor/agent)
else
H.equipOutfit(/datum/outfit/abductor/scientist)
/datum/team/abductor_team
member_name = "abductor"
var/team_number
var/list/datum/mind/abductees = list()
var/static/team_count = 1
/datum/team/abductor_team/New()
..()
team_number = team_count++
name = "Mothership [pick(GLOB.possible_changeling_IDs)]" //TODO Ensure unique and actual alieny names
add_objective(new/datum/objective/experiment)
/datum/team/abductor_team/is_solo()
return FALSE
/datum/team/abductor_team/proc/add_objective(datum/objective/O)
O.team = src
O.update_explanation_text()
objectives += O
/datum/team/abductor_team/roundend_report()
var/list/result = list()
var/won = TRUE
for(var/datum/objective/O in objectives)
if(!O.check_completion())
won = FALSE
if(won)
result += "<span class='greentext big'>[name] team fulfilled its mission!</span>"
else
result += "<span class='redtext big'>[name] team failed its mission.</span>"
result += "<span class='header'>The abductors of [name] were:</span>"
for(var/datum/mind/abductor_mind in members)
result += printplayer(abductor_mind)
result += printobjectives(abductor_mind)
return "<div class='panel redborder'>[result.Join("<br>")]</div>"
/datum/antagonist/abductee
name = "Abductee"
roundend_category = "abductees"
antagpanel_category = "Abductee"
/datum/antagonist/abductee/on_gain()
give_objective()
. = ..()
/datum/antagonist/abductee/greet()
to_chat(owner, "<span class='warning'><b>Your mind snaps!</b></span>")
to_chat(owner, "<big><span class='warning'><b>You can't remember how you got here...</b></span></big>")
owner.announce_objectives()
/datum/antagonist/abductee/proc/give_objective()
var/mob/living/carbon/human/H = owner.current
if(istype(H))
H.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_LOBOTOMY)
var/objtype = (prob(75) ? /datum/objective/abductee/random : pick(subtypesof(/datum/objective/abductee/) - /datum/objective/abductee/random))
var/datum/objective/abductee/O = new objtype()
objectives += O
owner.objectives += objectives
/datum/antagonist/abductee/apply_innate_effects(mob/living/mob_override)
update_abductor_icons_added(mob_override ? mob_override.mind : owner,"abductee")
/datum/antagonist/abductee/remove_innate_effects(mob/living/mob_override)
update_abductor_icons_removed(mob_override ? mob_override.mind : owner)
// LANDMARKS
/obj/effect/landmark/abductor
var/team_number = 1
/obj/effect/landmark/abductor/agent
icon_state = "abductor_agent"
/obj/effect/landmark/abductor/scientist
icon_state = "abductor"
// OBJECTIVES
/datum/objective/experiment
target_amount = 6
/datum/objective/experiment/New()
explanation_text = "Experiment on [target_amount] humans."
/datum/objective/experiment/check_completion()
for(var/obj/machinery/abductor/experiment/E in GLOB.machines)
if(!istype(team, /datum/team/abductor_team))
return FALSE
var/datum/team/abductor_team/T = team
if(E.team_number == T.team_number)
return E.points >= target_amount
return FALSE
/datum/antagonist/proc/update_abductor_icons_added(datum/mind/alien_mind,hud_type)
var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR]
hud.join_hud(alien_mind.current)
set_antag_hud(alien_mind.current, hud_type)
/datum/antagonist/proc/update_abductor_icons_removed(datum/mind/alien_mind)
var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR]
hud.leave_hud(alien_mind.current)
set_antag_hud(alien_mind.current, null)
@@ -0,0 +1,797 @@
#define VEST_STEALTH 1
#define VEST_COMBAT 2
#define GIZMO_SCAN 1
#define GIZMO_MARK 2
#define MIND_DEVICE_MESSAGE 1
#define MIND_DEVICE_CONTROL 2
//AGENT VEST
/obj/item/clothing/suit/armor/abductor/vest
name = "agent vest"
desc = "A vest outfitted with advanced stealth technology. It has two modes - combat and stealth."
icon = 'icons/obj/abductor.dmi'
icon_state = "vest_stealth"
item_state = "armor"
blood_overlay_type = "armor"
armor = list("melee" = 15, "bullet" = 15, "laser" = 15, "energy" = 15, "bomb" = 15, "bio" = 15, "rad" = 15, "fire" = 70, "acid" = 70)
actions_types = list(/datum/action/item_action/hands_free/activate)
allowed = list(
/obj/item/abductor,
/obj/item/abductor_baton,
/obj/item/melee/baton,
/obj/item/gun/energy,
/obj/item/restraints/handcuffs
)
var/mode = VEST_STEALTH
var/stealth_active = 0
var/combat_cooldown = 10
var/datum/icon_snapshot/disguise
var/stealth_armor = list("melee" = 15, "bullet" = 15, "laser" = 15, "energy" = 15, "bomb" = 15, "bio" = 15, "rad" = 15, "fire" = 70, "acid" = 70)
var/combat_armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 50, "rad" = 50, "fire" = 90, "acid" = 90)
/obj/item/clothing/suit/armor/abductor/vest/proc/toggle_nodrop()
if(HAS_TRAIT_FROM(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT))
REMOVE_TRAIT(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT)
else
ADD_TRAIT(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT)
if(ismob(loc))
to_chat(loc, "<span class='notice'>Your vest is now [HAS_TRAIT_FROM(src, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT) ? "locked" : "unlocked"].</span>")
/obj/item/clothing/suit/armor/abductor/vest/proc/flip_mode()
switch(mode)
if(VEST_STEALTH)
mode = VEST_COMBAT
DeactivateStealth()
armor = combat_armor
icon_state = "vest_combat"
if(VEST_COMBAT)// TO STEALTH
mode = VEST_STEALTH
armor = stealth_armor
icon_state = "vest_stealth"
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
H.update_inv_wear_suit()
for(var/X in actions)
var/datum/action/A = X
A.UpdateButtonIcon()
/obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user)
if(slot == SLOT_WEAR_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it.
return 1
/obj/item/clothing/suit/armor/abductor/vest/proc/SetDisguise(datum/icon_snapshot/entry)
disguise = entry
/obj/item/clothing/suit/armor/abductor/vest/proc/ActivateStealth()
if(disguise == null)
return
stealth_active = 1
if(ishuman(loc))
var/mob/living/carbon/human/M = loc
new /obj/effect/temp_visual/dir_setting/ninja/cloak(get_turf(M), M.dir)
M.name_override = disguise.name
M.icon = disguise.icon
M.icon_state = disguise.icon_state
M.cut_overlays()
M.add_overlay(disguise.overlays)
M.update_inv_hands()
/obj/item/clothing/suit/armor/abductor/vest/proc/DeactivateStealth()
if(!stealth_active)
return
stealth_active = 0
if(ishuman(loc))
var/mob/living/carbon/human/M = loc
new /obj/effect/temp_visual/dir_setting/ninja(get_turf(M), M.dir)
M.name_override = null
M.cut_overlays()
M.regenerate_icons()
/obj/item/clothing/suit/armor/abductor/vest/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
DeactivateStealth()
return 0
/obj/item/clothing/suit/armor/abductor/vest/IsReflect()
DeactivateStealth()
return 0
/obj/item/clothing/suit/armor/abductor/vest/ui_action_click()
switch(mode)
if(VEST_COMBAT)
Adrenaline()
if(VEST_STEALTH)
if(stealth_active)
DeactivateStealth()
else
ActivateStealth()
/obj/item/clothing/suit/armor/abductor/vest/proc/Adrenaline()
if(ishuman(loc))
if(combat_cooldown != initial(combat_cooldown))
to_chat(loc, "<span class='warning'>Combat injection is still recharging.</span>")
return
var/mob/living/carbon/human/M = loc
M.do_adrenaline(150, FALSE, 0, 0, TRUE, list("inaprovaline" = 3, "synaptizine" = 10, "omnizine" = 10), "<span class='boldnotice'>You feel a sudden surge of energy!</span>")
combat_cooldown = 0
START_PROCESSING(SSobj, src)
/obj/item/clothing/suit/armor/abductor/vest/process()
combat_cooldown++
if(combat_cooldown==initial(combat_cooldown))
STOP_PROCESSING(SSobj, src)
/obj/item/clothing/suit/armor/abductor/Destroy()
STOP_PROCESSING(SSobj, src)
for(var/obj/machinery/abductor/console/C in GLOB.machines)
if(C.vest == src)
C.vest = null
break
. = ..()
/obj/item/abductor
icon = 'icons/obj/abductor.dmi'
/obj/item/abductor/proc/AbductorCheck(user)
if(isabductor(user))
return TRUE
to_chat(user, "<span class='warning'>You can't figure how this works!</span>")
return FALSE
/obj/item/abductor/proc/ScientistCheck(user)
if(!AbductorCheck(user))
return FALSE
var/mob/living/carbon/human/H = user
var/datum/species/abductor/S = H.dna.species
if(S.scientist)
return TRUE
to_chat(user, "<span class='warning'>You're not trained to use this!</span>")
return FALSE
/obj/item/abductor/gizmo
name = "science tool"
desc = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras."
icon_state = "gizmo_scan"
item_state = "silencer"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
var/mode = GIZMO_SCAN
var/mob/living/marked = null
var/obj/machinery/abductor/console/console
/obj/item/abductor/gizmo/attack_self(mob/user)
if(!ScientistCheck(user))
return
if(!console)
to_chat(user, "<span class='warning'>The device is not linked to console!</span>")
return
if(mode == GIZMO_SCAN)
mode = GIZMO_MARK
icon_state = "gizmo_mark"
else
mode = GIZMO_SCAN
icon_state = "gizmo_scan"
to_chat(user, "<span class='notice'>You switch the device to [mode==GIZMO_SCAN? "SCAN": "MARK"] MODE</span>")
/obj/item/abductor/gizmo/attack(mob/living/M, mob/user)
if(!ScientistCheck(user))
return
if(!console)
to_chat(user, "<span class='warning'>The device is not linked to console!</span>")
return
switch(mode)
if(GIZMO_SCAN)
scan(M, user)
if(GIZMO_MARK)
mark(M, user)
/obj/item/abductor/gizmo/afterattack(atom/target, mob/living/user, flag, params)
. = ..()
if(flag)
return
if(!ScientistCheck(user))
return
if(!console)
to_chat(user, "<span class='warning'>The device is not linked to console!</span>")
return
switch(mode)
if(GIZMO_SCAN)
scan(target, user)
if(GIZMO_MARK)
mark(target, user)
/obj/item/abductor/gizmo/proc/scan(atom/target, mob/living/user)
if(ishuman(target))
console.AddSnapshot(target)
to_chat(user, "<span class='notice'>You scan [target] and add [target.p_them()] to the database.</span>")
/obj/item/abductor/gizmo/proc/mark(atom/target, mob/living/user)
if(marked == target)
to_chat(user, "<span class='warning'>This specimen is already marked!</span>")
return
if(ishuman(target))
if(isabductor(target))
marked = target
to_chat(user, "<span class='notice'>You mark [target] for future retrieval.</span>")
else
prepare(target,user)
else
prepare(target,user)
/obj/item/abductor/gizmo/proc/prepare(atom/target, mob/living/user)
if(get_dist(target,user)>1)
to_chat(user, "<span class='warning'>You need to be next to the specimen to prepare it for transport!</span>")
return
to_chat(user, "<span class='notice'>You begin preparing [target] for transport...</span>")
if(do_after(user, 100, target = target))
marked = target
to_chat(user, "<span class='notice'>You finish preparing [target] for transport.</span>")
/obj/item/abductor/gizmo/Destroy()
if(console)
console.gizmo = null
. = ..()
/obj/item/abductor/silencer
name = "abductor silencer"
desc = "A compact device used to shut down communications equipment."
icon_state = "silencer"
item_state = "gizmo"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
/obj/item/abductor/silencer/attack(mob/living/M, mob/user)
if(!AbductorCheck(user))
return
radio_off(M, user)
/obj/item/abductor/silencer/afterattack(atom/target, mob/living/user, flag, params)
. = ..()
if(flag)
return
if(!AbductorCheck(user))
return
radio_off(target, user)
/obj/item/abductor/silencer/proc/radio_off(atom/target, mob/living/user)
if( !(user in (viewers(7,target))) )
return
var/turf/targloc = get_turf(target)
var/mob/living/carbon/human/M
for(M in view(2,targloc))
if(M == user)
continue
to_chat(user, "<span class='notice'>You silence [M]'s radio devices.</span>")
radio_off_mob(M)
/obj/item/abductor/silencer/proc/radio_off_mob(mob/living/carbon/human/M)
var/list/all_items = M.GetAllContents()
for(var/obj/I in all_items)
if(istype(I, /obj/item/radio/))
var/obj/item/radio/r = I
r.listening = 0
if(!istype(I, /obj/item/radio/headset))
r.broadcasting = 0 //goddamned headset hacks
/obj/item/abductor/mind_device
name = "mental interface device"
desc = "A dual-mode tool for directly communicating with sentient brains. It can be used to send a direct message to a target, \
or to send a command to a test subject with a charged gland."
icon_state = "mind_device_message"
item_state = "silencer"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
var/mode = MIND_DEVICE_MESSAGE
/obj/item/abductor/mind_device/attack_self(mob/user)
if(!ScientistCheck(user))
return
if(mode == MIND_DEVICE_MESSAGE)
mode = MIND_DEVICE_CONTROL
icon_state = "mind_device_control"
else
mode = MIND_DEVICE_MESSAGE
icon_state = "mind_device_message"
to_chat(user, "<span class='notice'>You switch the device to [mode==MIND_DEVICE_MESSAGE? "TRANSMISSION": "COMMAND"] MODE</span>")
/obj/item/abductor/mind_device/afterattack(atom/target, mob/living/user, flag, params)
. = ..()
if(!ScientistCheck(user))
return
switch(mode)
if(MIND_DEVICE_CONTROL)
mind_control(target, user)
if(MIND_DEVICE_MESSAGE)
mind_message(target, user)
/obj/item/abductor/mind_device/proc/mind_control(atom/target, mob/living/user)
if(iscarbon(target))
var/mob/living/carbon/C = target
var/obj/item/organ/heart/gland/G = C.getorganslot("heart")
if(!istype(G))
to_chat(user, "<span class='warning'>Your target does not have an experimental gland!</span>")
return
if(!G.mind_control_uses)
to_chat(user, "<span class='warning'>Your target's gland is spent!</span>")
return
if(G.active_mind_control)
to_chat(user, "<span class='warning'>Your target is already under a mind-controlling influence!</span>")
return
var/command = stripped_input(user, "Enter the command for your target to follow.\
Uses Left: [G.mind_control_uses], Duration: [DisplayTimeText(G.mind_control_duration)]","Enter command")
if(!command)
return
if(QDELETED(user) || user.get_active_held_item() != src || loc != user)
return
if(QDELETED(G))
return
if(istype(C.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(user, "<span class='warning'>Your target seems to have some sort of protective headgear on, blocking the message from being sent!</span>")
return
G.mind_control(command, user)
to_chat(user, "<span class='notice'>You send the command to your target.</span>")
/obj/item/abductor/mind_device/proc/mind_message(atom/target, mob/living/user)
if(isliving(target))
var/mob/living/L = target
if(L.stat == DEAD)
to_chat(user, "<span class='warning'>Your target is dead!</span>")
return
var/message = stripped_input(user, "Write a message to send to your target's brain.","Enter message")
if(!message)
return
if(QDELETED(L) || L.stat == DEAD)
return
to_chat(L, "<span class='italics'>You hear a voice in your head saying: </span><span class='abductor'>[message]</span>")
to_chat(user, "<span class='notice'>You send the message to your target.</span>")
log_directed_talk(user, L, message, LOG_SAY, "abductor whisper")
/obj/item/firing_pin/abductor
name = "alien firing pin"
icon_state = "firing_pin_ayy"
desc = "This firing pin is slimy and warm; you can swear you feel it \
constantly trying to mentally probe you."
fail_message = "<span class='abductor'>\
Firing error, please contact Command.</span>"
/obj/item/firing_pin/abductor/pin_auth(mob/living/user)
. = isabductor(user)
/obj/item/gun/energy/alien
name = "alien pistol"
desc = "A complicated gun that fires bursts of high-intensity radiation."
ammo_type = list(/obj/item/ammo_casing/energy/declone)
pin = /obj/item/firing_pin/abductor
icon_state = "alienpistol"
item_state = "alienpistol"
trigger_guard = TRIGGER_GUARD_ALLOW_ALL
/obj/item/paper/guides/antag/abductor
name = "Dissection Guide"
icon_state = "alienpaper_words"
info = {"<b>Dissection for Dummies</b><br>
<br>
1.Acquire fresh specimen.<br>
2.Put the specimen on operating table.<br>
3.Apply surgical drapes, preparing for experimental dissection.<br>
4.Apply scalpel to specimen's torso.<br>
5.Clamp bleeders on specimen's torso with a hemostat.<br>
6.Retract skin of specimen's torso with a retractor.<br>
7.Apply scalpel again to specimen's torso.<br>
8.Search through the specimen's torso with your hands to remove any superfluous organs.<br>
9.Insert replacement gland (Retrieve one from gland storage).<br>
10.Consider dressing the specimen back to not disturb the habitat. <br>
11.Put the specimen in the experiment machinery.<br>
12.Choose one of the machine options. The target will be analyzed and teleported to the selected drop-off point.<br>
13.You will receive one supply credit, and the subject will be counted towards your quota.<br>
<br>
Congratulations! You are now trained for invasive xenobiology research!"}
/obj/item/paper/guides/antag/abductor/update_icon()
return
/obj/item/paper/guides/antag/abductor/AltClick()
return //otherwise it would fold into a paperplane.
#define BATON_STUN 0
#define BATON_SLEEP 1
#define BATON_CUFF 2
#define BATON_PROBE 3
#define BATON_MODES 4
/obj/item/abductor_baton
name = "advanced baton"
desc = "A quad-mode baton used for incapacitation and restraining of specimens."
var/mode = BATON_STUN
icon = 'icons/obj/abductor.dmi'
icon_state = "wonderprodStun"
item_state = "wonderprod"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
force = 7
w_class = WEIGHT_CLASS_NORMAL
actions_types = list(/datum/action/item_action/toggle_mode)
/obj/item/abductor_baton/proc/toggle(mob/living/user=usr)
mode = (mode+1)%BATON_MODES
var/txt
switch(mode)
if(BATON_STUN)
txt = "stunning"
if(BATON_SLEEP)
txt = "sleep inducement"
if(BATON_CUFF)
txt = "restraining"
if(BATON_PROBE)
txt = "probing"
to_chat(usr, "<span class='notice'>You switch the baton to [txt] mode.</span>")
update_icon()
/obj/item/abductor_baton/update_icon()
switch(mode)
if(BATON_STUN)
icon_state = "wonderprodStun"
item_state = "wonderprodStun"
if(BATON_SLEEP)
icon_state = "wonderprodSleep"
item_state = "wonderprodSleep"
if(BATON_CUFF)
icon_state = "wonderprodCuff"
item_state = "wonderprodCuff"
if(BATON_PROBE)
icon_state = "wonderprodProbe"
item_state = "wonderprodProbe"
/obj/item/abductor_baton/attack(mob/target, mob/living/user)
if(!isabductor(user))
return
if(iscyborg(target))
..()
return
if(!isliving(target))
return
var/mob/living/L = target
user.do_attack_animation(L)
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
return 0
switch (mode)
if(BATON_STUN)
StunAttack(L,user)
if(BATON_SLEEP)
SleepAttack(L,user)
if(BATON_CUFF)
CuffAttack(L,user)
if(BATON_PROBE)
ProbeAttack(L,user)
/obj/item/abductor_baton/attack_self(mob/living/user)
toggle(user)
/obj/item/abductor_baton/proc/StunAttack(mob/living/L,mob/living/user)
L.lastattacker = user.real_name
L.lastattackerckey = user.ckey
L.adjustStaminaLoss(35) //because previously it took 5-6 hits to actually "incapacitate" someone for the purposes of the sleep inducement
L.Knockdown(140)
L.apply_effect(EFFECT_STUTTER, 7)
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK)
L.visible_message("<span class='danger'>[user] has stunned [L] with [src]!</span>", \
"<span class='userdanger'>[user] has stunned you with [src]!</span>")
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
if(ishuman(L))
var/mob/living/carbon/human/H = L
H.forcesay(GLOB.hit_appends)
log_combat(user, L, "stunned")
/obj/item/abductor_baton/proc/SleepAttack(mob/living/L,mob/living/user)
if(L.incapacitated(TRUE, TRUE))
if(istype(L.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(user, "<span class='warning'>The specimen's protective headgear is interfering with the sleep inducement!</span>")
L.visible_message("<span class='danger'>[user] tried to induced sleep in [L] with [src], but [L.p_their()] headgear protected [L.p_them()]!</span>", \
"<span class='userdanger'>You feel a strange wave of heavy drowsiness wash over you, but your headgear deflects most of it!</span>")
L.drowsyness += 2
return
L.visible_message("<span class='danger'>[user] has induced sleep in [L] with [src]!</span>", \
"<span class='userdanger'>You suddenly feel very drowsy!</span>")
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
L.Sleeping(1200)
log_combat(user, L, "put to sleep")
else
if(istype(L.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
to_chat(user, "<span class='warning'>The specimen's protective headgear is completely blocking our sleep inducement methods!</span>")
L.visible_message("<span class='danger'>[user] tried to induce sleep in [L] with [src], but [L.p_their()] headgear completely protected [L.p_them()]!</span>", \
"<span class='userdanger'>Any sense of drowsiness is quickly diminished as your headgear deflects the effects!</span>")
return
L.drowsyness += 1
to_chat(user, "<span class='warning'>Sleep inducement works fully only on stunned specimens! </span>")
L.visible_message("<span class='danger'>[user] tried to induce sleep in [L] with [src]!</span>", \
"<span class='userdanger'>You suddenly feel drowsy!</span>")
/obj/item/abductor_baton/proc/CuffAttack(mob/living/L,mob/living/user)
if(!iscarbon(L))
return
var/mob/living/carbon/C = L
if(!C.handcuffed)
if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())
playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2)
C.visible_message("<span class='danger'>[user] begins restraining [C] with [src]!</span>", \
"<span class='userdanger'>[user] begins shaping an energy field around your hands!</span>")
if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()))
if(!C.handcuffed)
C.handcuffed = new /obj/item/restraints/handcuffs/energy/used(C)
C.update_handcuffed()
to_chat(user, "<span class='notice'>You restrain [C].</span>")
log_combat(user, C, "handcuffed")
else
to_chat(user, "<span class='warning'>You fail to restrain [C].</span>")
else
to_chat(user, "<span class='warning'>[C] doesn't have two hands...</span>")
/obj/item/abductor_baton/proc/ProbeAttack(mob/living/L,mob/living/user)
L.visible_message("<span class='danger'>[user] probes [L] with [src]!</span>", \
"<span class='userdanger'>[user] probes you!</span>")
var/species = "<span class='warning'>Unknown species</span>"
var/helptext = "<span class='warning'>Species unsuitable for experiments.</span>"
if(ishuman(L))
var/mob/living/carbon/human/H = L
species = "<span class='notice'>[H.dna.species.name]</span>"
if(L.mind && L.mind.has_antag_datum(/datum/antagonist/changeling))
species = "<span class='warning'>Changeling lifeform</span>"
var/obj/item/organ/heart/gland/temp = locate() in H.internal_organs
if(temp)
helptext = "<span class='warning'>Experimental gland detected!</span>"
else
if (L.getorganslot(ORGAN_SLOT_HEART))
helptext = "<span class='notice'>Subject suitable for experiments.</span>"
else
helptext = "<span class='warning'>Subject unsuitable for experiments.</span>"
to_chat(user, "<span class='notice'>Probing result:</span>[species]")
to_chat(user, "[helptext]")
/obj/item/restraints/handcuffs/energy
name = "hard-light energy field"
desc = "A hard-light field restraining the hands."
icon_state = "cuff" // Needs sprite
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
breakouttime = 450
trashtype = /obj/item/restraints/handcuffs/energy/used
flags_1 = NONE
/obj/item/restraints/handcuffs/energy/used
item_flags = DROPDEL
/obj/item/restraints/handcuffs/energy/used/dropped(mob/user)
user.visible_message("<span class='danger'>[user]'s [name] breaks in a discharge of energy!</span>", \
"<span class='userdanger'>[user]'s [name] breaks in a discharge of energy!</span>")
var/datum/effect_system/spark_spread/S = new
S.set_up(4,0,user.loc)
S.start()
. = ..()
/obj/item/abductor_baton/examine(mob/user)
..()
switch(mode)
if(BATON_STUN)
to_chat(user, "<span class='warning'>The baton is in stun mode.</span>")
if(BATON_SLEEP)
to_chat(user, "<span class='warning'>The baton is in sleep inducement mode.</span>")
if(BATON_CUFF)
to_chat(user, "<span class='warning'>The baton is in restraining mode.</span>")
if(BATON_PROBE)
to_chat(user, "<span class='warning'>The baton is in probing mode.</span>")
/obj/item/radio/headset/abductor
name = "alien headset"
desc = "An advanced alien headset designed to monitor communications of human space stations. Why does it have a microphone? No one knows."
icon = 'icons/obj/abductor.dmi'
icon_state = "abductor_headset"
item_state = "abductor_headset"
keyslot2 = new /obj/item/encryptionkey/heads/captain
/obj/item/radio/headset/abductor/Initialize(mapload)
. = ..()
make_syndie()
/obj/item/radio/headset/abductor/ComponentInitialize()
. = ..()
AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS))
/obj/item/radio/headset/abductor/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/screwdriver))
return // Stops humans from disassembling abductor headsets.
return ..()
/obj/item/scalpel/alien
name = "alien scalpel"
desc = "It's a gleaming sharp knife made out of silvery-green metal."
icon = 'icons/obj/abductor.dmi'
toolspeed = 0.25
/obj/item/hemostat/alien
name = "alien hemostat"
desc = "You've never seen this before."
icon = 'icons/obj/abductor.dmi'
toolspeed = 0.25
/obj/item/retractor/alien
name = "alien retractor"
desc = "You're not sure if you want the veil pulled back."
icon = 'icons/obj/abductor.dmi'
toolspeed = 0.25
/obj/item/circular_saw/alien
name = "alien saw"
desc = "Do the aliens also lose this, and need to find an alien hatchet?"
icon = 'icons/obj/abductor.dmi'
toolspeed = 0.25
/obj/item/surgicaldrill/alien
name = "alien drill"
desc = "Maybe alien surgeons have finally found a use for the drill."
icon = 'icons/obj/abductor.dmi'
toolspeed = 0.25
/obj/item/cautery/alien
name = "alien cautery"
desc = "Why would bloodless aliens have a tool to stop bleeding? \
Unless..."
icon = 'icons/obj/abductor.dmi'
toolspeed = 0.25
/obj/item/clothing/head/helmet/abductor
name = "agent headgear"
desc = "Abduct with style - spiky style. Prevents digital tracking."
icon_state = "alienhelmet"
item_state = "alienhelmet"
blockTracking = 1
flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
// Operating Table / Beds / Lockers
/obj/structure/bed/abductor
name = "resting contraption"
desc = "This looks similar to contraptions from Earth. Could aliens be stealing our technology?"
icon = 'icons/obj/abductor.dmi'
buildstacktype = /obj/item/stack/sheet/mineral/abductor
icon_state = "bed"
/obj/structure/table_frame/abductor
name = "alien table frame"
desc = "A sturdy table frame made from alien alloy."
icon_state = "alien_frame"
framestack = /obj/item/stack/sheet/mineral/abductor
framestackamount = 1
/obj/structure/table_frame/abductor/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wrench))
to_chat(user, "<span class='notice'>You start disassembling [src]...</span>")
I.play_tool_sound(src)
if(I.use_tool(src, user, 30))
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
for(var/i = 1, i <= framestackamount, i++)
new framestack(get_turf(src))
qdel(src)
return
if(istype(I, /obj/item/stack/sheet/mineral/abductor))
var/obj/item/stack/sheet/P = I
if(P.get_amount() < 1)
to_chat(user, "<span class='warning'>You need one alien alloy sheet to do this!</span>")
return
to_chat(user, "<span class='notice'>You start adding [P] to [src]...</span>")
if(do_after(user, 50, target = src))
P.use(1)
new /obj/structure/table/abductor(src.loc)
qdel(src)
return
if(istype(I, /obj/item/stack/sheet/mineral/silver))
var/obj/item/stack/sheet/P = I
if(P.get_amount() < 1)
to_chat(user, "<span class='warning'>You need one sheet of silver to do this!</span>")
return
to_chat(user, "<span class='notice'>You start adding [P] to [src]...</span>")
if(do_after(user, 50, target = src))
P.use(1)
new /obj/structure/table/optable/abductor(src.loc)
qdel(src)
/obj/structure/table/abductor
name = "alien table"
desc = "Advanced flat surface technology at work!"
icon = 'icons/obj/smooth_structures/alien_table.dmi'
icon_state = "alien_table"
buildstack = /obj/item/stack/sheet/mineral/abductor
framestack = /obj/item/stack/sheet/mineral/abductor
buildstackamount = 1
framestackamount = 1
canSmoothWith = null
frame = /obj/structure/table_frame/abductor
/obj/structure/table/optable/abductor
name = "alien operating table"
desc = "Used for alien medical procedures. The surface is covered in tiny spines."
frame = /obj/structure/table_frame/abductor
buildstack = /obj/item/stack/sheet/mineral/silver
framestack = /obj/item/stack/sheet/mineral/abductor
buildstackamount = 1
framestackamount = 1
icon = 'icons/obj/abductor.dmi'
icon_state = "bed"
can_buckle = 1
buckle_lying = 1
var/static/list/injected_reagents = list("corazone")
/obj/structure/table/optable/abductor/Crossed(atom/movable/AM)
. = ..()
if(iscarbon(AM))
START_PROCESSING(SSobj, src)
to_chat(AM, "<span class='danger'>You feel a series of tiny pricks!</span>")
/obj/structure/table/optable/abductor/process()
. = PROCESS_KILL
for(var/mob/living/carbon/C in get_turf(src))
. = TRUE
for(var/chemical in injected_reagents)
if(C.reagents.get_reagent_amount(chemical) < 1)
C.reagents.add_reagent(chemical, 1)
/obj/structure/table/optable/abductor/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
/obj/structure/closet/abductor
name = "alien locker"
desc = "Contains secrets of the universe."
icon_state = "abductor"
icon_door = "abductor"
can_weld_shut = FALSE
material_drop = /obj/item/stack/sheet/mineral/abductor
/obj/structure/door_assembly/door_assembly_abductor
name = "alien airlock assembly"
icon = 'icons/obj/doors/airlocks/abductor/abductor_airlock.dmi'
base_name = "alien airlock"
overlays_file = 'icons/obj/doors/airlocks/abductor/overlays.dmi'
airlock_type = /obj/machinery/door/airlock/abductor
material_type = /obj/item/stack/sheet/mineral/abductor
noglass = TRUE
@@ -0,0 +1,56 @@
/datum/outfit/abductor
name = "Abductor Basic"
uniform = /obj/item/clothing/under/color/grey //they're greys gettit
shoes = /obj/item/clothing/shoes/combat
back = /obj/item/storage/backpack
ears = /obj/item/radio/headset/abductor
/datum/outfit/abductor/proc/link_to_console(mob/living/carbon/human/H, team_number)
var/datum/antagonist/abductor/A = H.mind.has_antag_datum(/datum/antagonist/abductor)
if(!team_number && A)
team_number = A.team.team_number
if(!team_number)
team_number = 1
var/obj/machinery/abductor/console/console = get_abductor_console(team_number)
if(console)
var/obj/item/clothing/suit/armor/abductor/vest/V = locate() in H
if(V)
console.AddVest(V)
ADD_TRAIT(V, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT)
var/obj/item/storage/backpack/B = locate() in H
if(B)
for(var/obj/item/abductor/gizmo/G in B.contents)
console.AddGizmo(G)
/datum/outfit/abductor/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
..()
if(!visualsOnly)
link_to_console(H)
/datum/outfit/abductor/agent
name = "Abductor Agent"
head = /obj/item/clothing/head/helmet/abductor
suit = /obj/item/clothing/suit/armor/abductor/vest
suit_store = /obj/item/abductor_baton
belt = /obj/item/storage/belt/military/abductor/full
backpack_contents = list(
/obj/item/gun/energy/alien = 1,
/obj/item/abductor/silencer = 1
)
/datum/outfit/abductor/scientist
name = "Abductor Scientist"
backpack_contents = list(
/obj/item/abductor/gizmo = 1
)
/datum/outfit/abductor/scientist/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
..()
if(!visualsOnly)
var/obj/item/implant/abductor/beamplant = new /obj/item/implant/abductor(H)
beamplant.implant(H)
@@ -0,0 +1,55 @@
/datum/surgery/organ_extraction
name = "experimental dissection"
steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/incise, /datum/surgery_step/extract_organ, /datum/surgery_step/gland_insert)
possible_locs = list(BODY_ZONE_CHEST)
ignore_clothes = 1
/datum/surgery/organ_extraction/can_start(mob/user, mob/living/carbon/target)
if(!ishuman(user))
return 0
var/mob/living/carbon/human/H = user
if(H.dna.species.id == "abductor")
return 1
for(var/obj/item/implant/abductor/A in H.implants)
return 1
return 0
/datum/surgery_step/extract_organ
name = "remove heart"
accept_hand = 1
time = 32
var/obj/item/organ/IC = null
var/list/organ_types = list(/obj/item/organ/heart)
/datum/surgery_step/extract_organ/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
for(var/atom/A in target.internal_organs)
if(A.type in organ_types)
IC = A
break
user.visible_message("[user] starts to remove [target]'s organs.", "<span class='notice'>You start to remove [target]'s organs...</span>")
/datum/surgery_step/extract_organ/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
if(IC)
user.visible_message("[user] pulls [IC] out of [target]'s [target_zone]!", "<span class='notice'>You pull [IC] out of [target]'s [target_zone].</span>")
user.put_in_hands(IC)
IC.Remove(target)
return 1
else
to_chat(user, "<span class='warning'>You don't find anything in [target]'s [target_zone]!</span>")
return 1
/datum/surgery_step/gland_insert
name = "insert gland"
implements = list(/obj/item/organ/heart/gland = 100)
time = 32
/datum/surgery_step/gland_insert/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
user.visible_message("[user] starts to insert [tool] into [target].", "<span class ='notice'>You start to insert [tool] into [target]...</span>")
/datum/surgery_step/gland_insert/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
user.visible_message("[user] inserts [tool] into [target].", "<span class ='notice'>You insert [tool] into [target].</span>")
user.temporarilyRemoveItemFromInventory(tool, TRUE)
var/obj/item/organ/heart/gland/gland = tool
gland.Insert(target, 2)
return 1
@@ -0,0 +1,336 @@
/obj/item/organ/heart/gland
name = "fleshy mass"
desc = "A nausea-inducing hunk of twisting flesh and metal."
icon = 'icons/obj/abductor.dmi'
icon_state = "gland"
status = ORGAN_ROBOTIC
beating = TRUE
var/cooldown_low = 300
var/cooldown_high = 300
var/next_activation = 0
var/uses // -1 For infinite
var/human_only = 0
var/active = 0
var/mind_control_uses = 1
var/mind_control_duration = 1800
var/active_mind_control = FALSE
/obj/item/organ/heart/gland/proc/ownerCheck()
if(ishuman(owner))
return TRUE
if(!human_only && iscarbon(owner))
return TRUE
return FALSE
/obj/item/organ/heart/gland/proc/Start()
active = 1
next_activation = world.time + rand(cooldown_low,cooldown_high)
/obj/item/organ/heart/gland/proc/update_gland_hud()
if(!owner)
return
var/image/holder = owner.hud_list[GLAND_HUD]
var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
holder.pixel_y = I.Height() - world.icon_size
if(active_mind_control)
holder.icon_state = "hudgland_active"
else if(mind_control_uses)
holder.icon_state = "hudgland_ready"
else
holder.icon_state = "hudgland_spent"
/obj/item/organ/heart/gland/proc/mind_control(command, mob/living/user)
if(!ownerCheck() || !mind_control_uses || active_mind_control)
return
mind_control_uses--
to_chat(owner, "<span class='userdanger'>You suddenly feel an irresistible compulsion to follow an order...</span>")
to_chat(owner, "<span class='mind_control'>[command]</span>")
active_mind_control = TRUE
log_admin("[key_name(user)] sent an abductor mind control message to [key_name(owner)]: [command]")
update_gland_hud()
addtimer(CALLBACK(src, .proc/clear_mind_control), mind_control_duration)
/obj/item/organ/heart/gland/proc/clear_mind_control()
if(!ownerCheck() || !active_mind_control)
return
to_chat(owner, "<span class='userdanger'>You feel the compulsion fade, and you completely forget about your previous orders.</span>")
active_mind_control = FALSE
/obj/item/organ/heart/gland/Remove(mob/living/carbon/M, special = 0)
active = 0
if(initial(uses) == 1)
uses = initial(uses)
var/datum/atom_hud/abductor/hud = GLOB.huds[DATA_HUD_ABDUCTOR]
hud.remove_from_hud(owner)
clear_mind_control()
..()
/obj/item/organ/heart/gland/Insert(mob/living/carbon/M, special = 0)
..()
if(special != 2 && uses) // Special 2 means abductor surgery
Start()
var/datum/atom_hud/abductor/hud = GLOB.huds[DATA_HUD_ABDUCTOR]
hud.add_to_hud(owner)
update_gland_hud()
/obj/item/organ/heart/gland/on_life()
if(!beating)
// alien glands are immune to stopping.
beating = TRUE
if(!active)
return
if(!ownerCheck())
active = 0
return
if(next_activation <= world.time)
activate()
uses--
next_activation = world.time + rand(cooldown_low,cooldown_high)
if(!uses)
active = 0
/obj/item/organ/heart/gland/proc/activate()
return
/obj/item/organ/heart/gland/heals
cooldown_low = 200
cooldown_high = 400
uses = -1
icon_state = "health"
mind_control_uses = 3
mind_control_duration = 3000
/obj/item/organ/heart/gland/heals/activate()
to_chat(owner, "<span class='notice'>You feel curiously revitalized.</span>")
owner.adjustToxLoss(-20, FALSE, TRUE)
owner.heal_bodypart_damage(20, 20, 0, TRUE)
owner.adjustOxyLoss(-20)
/obj/item/organ/heart/gland/slime
cooldown_low = 600
cooldown_high = 1200
uses = -1
icon_state = "slime"
mind_control_uses = 1
mind_control_duration = 2400
/obj/item/organ/heart/gland/slime/Insert(mob/living/carbon/M, special = 0)
..()
owner.faction |= "slime"
owner.grant_language(/datum/language/slime)
/obj/item/organ/heart/gland/slime/activate()
to_chat(owner, "<span class='warning'>You feel nauseated!</span>")
owner.vomit(20)
var/mob/living/simple_animal/slime/Slime = new(get_turf(owner), "grey")
Slime.Friends = list(owner)
Slime.Leader = owner
/obj/item/organ/heart/gland/mindshock
cooldown_low = 400
cooldown_high = 700
uses = -1
icon_state = "mindshock"
mind_control_uses = 1
mind_control_duration = 6000
/obj/item/organ/heart/gland/mindshock/activate()
to_chat(owner, "<span class='notice'>You get a headache.</span>")
var/turf/T = get_turf(owner)
for(var/mob/living/carbon/H in orange(4,T))
if(H == owner)
continue
switch(pick(1,3))
if(1)
to_chat(H, "<span class='userdanger'>You hear a loud buzz in your head, silencing your thoughts!</span>")
H.Stun(50)
if(2)
to_chat(H, "<span class='warning'>You hear an annoying buzz in your head.</span>")
H.confused += 15
H.adjustBrainLoss(10, 160)
if(3)
H.hallucination += 60
/obj/item/organ/heart/gland/pop
cooldown_low = 900
cooldown_high = 1800
uses = -1
human_only = TRUE
icon_state = "species"
mind_control_uses = 5
mind_control_duration = 300
/obj/item/organ/heart/gland/pop/activate()
to_chat(owner, "<span class='notice'>You feel unlike yourself.</span>")
randomize_human(owner)
var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/moth, /datum/species/fly))
owner.set_species(species)
/obj/item/organ/heart/gland/ventcrawling
cooldown_low = 1800
cooldown_high = 2400
uses = 1
icon_state = "vent"
mind_control_uses = 4
mind_control_duration = 1800
/obj/item/organ/heart/gland/ventcrawling/activate()
to_chat(owner, "<span class='notice'>You feel very stretchy.</span>")
owner.ventcrawler = VENTCRAWLER_ALWAYS
/obj/item/organ/heart/gland/viral
cooldown_low = 1800
cooldown_high = 2400
uses = 1
icon_state = "viral"
mind_control_uses = 1
mind_control_duration = 1800
/obj/item/organ/heart/gland/viral/activate()
to_chat(owner, "<span class='warning'>You feel sick.</span>")
var/datum/disease/advance/A = random_virus(pick(2,6),6)
A.carrier = TRUE
owner.ForceContractDisease(A, FALSE, TRUE)
/obj/item/organ/heart/gland/viral/proc/random_virus(max_symptoms, max_level)
if(max_symptoms > VIRUS_SYMPTOM_LIMIT)
max_symptoms = VIRUS_SYMPTOM_LIMIT
var/datum/disease/advance/A = new /datum/disease/advance()
var/list/datum/symptom/possible_symptoms = list()
for(var/symptom in subtypesof(/datum/symptom))
var/datum/symptom/S = symptom
if(initial(S.level) > max_level)
continue
if(initial(S.level) <= 0) //unobtainable symptoms
continue
possible_symptoms += S
for(var/i in 1 to max_symptoms)
var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms)
if(chosen_symptom)
var/datum/symptom/S = new chosen_symptom
A.symptoms += S
A.Refresh() //just in case someone already made and named the same disease
return A
/obj/item/organ/heart/gland/trauma
cooldown_low = 800
cooldown_high = 1200
uses = 5
icon_state = "emp"
mind_control_uses = 3
mind_control_duration = 1800
/obj/item/organ/heart/gland/trauma/activate()
to_chat(owner, "<span class='warning'>You feel a spike of pain in your head.</span>")
if(prob(33))
owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
else
if(prob(20))
owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
else
owner.gain_trauma_type(BRAIN_TRAUMA_MILD, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
/obj/item/organ/heart/gland/spiderman
cooldown_low = 450
cooldown_high = 900
uses = -1
icon_state = "spider"
mind_control_uses = 2
mind_control_duration = 2400
/obj/item/organ/heart/gland/spiderman/activate()
to_chat(owner, "<span class='warning'>You feel something crawling in your skin.</span>")
owner.faction |= "spiders"
var/obj/structure/spider/spiderling/S = new(owner.drop_location())
S.directive = "Protect your nest inside [owner.real_name]."
/obj/item/organ/heart/gland/egg
cooldown_low = 300
cooldown_high = 400
uses = -1
icon_state = "egg"
lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
mind_control_uses = 2
mind_control_duration = 1800
/obj/item/organ/heart/gland/egg/activate()
owner.visible_message("<span class='alertalien'>[owner] [pick(EGG_LAYING_MESSAGES)]</span>")
var/turf/T = owner.drop_location()
new /obj/item/reagent_containers/food/snacks/egg/gland(T)
/obj/item/organ/heart/gland/electric
cooldown_low = 800
cooldown_high = 1200
uses = -1
mind_control_uses = 2
mind_control_duration = 900
/obj/item/organ/heart/gland/electric/Insert(mob/living/carbon/M, special = 0)
..()
ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, ORGAN_TRAIT)
/obj/item/organ/heart/gland/electric/Remove(mob/living/carbon/M, special = 0)
REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, ORGAN_TRAIT)
..()
/obj/item/organ/heart/gland/electric/activate()
owner.visible_message("<span class='danger'>[owner]'s skin starts emitting electric arcs!</span>",\
"<span class='warning'>You feel electric energy building up inside you!</span>")
playsound(get_turf(owner), "sparks", 100, 1, -1)
addtimer(CALLBACK(src, .proc/zap), rand(30, 100))
/obj/item/organ/heart/gland/electric/proc/zap()
tesla_zap(owner, 4, 8000, TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN)
playsound(get_turf(owner), 'sound/magic/lightningshock.ogg', 50, 1)
/obj/item/organ/heart/gland/chem
cooldown_low = 50
cooldown_high = 50
uses = -1
mind_control_uses = 3
mind_control_duration = 1200
var/list/possible_reagents = list()
/obj/item/organ/heart/gland/chem/Initialize()
..()
for(var/X in subtypesof(/datum/reagent/drug))
var/datum/reagent/R = X
possible_reagents += initial(R.id)
for(var/X in subtypesof(/datum/reagent/medicine))
var/datum/reagent/R = X
possible_reagents += initial(R.id)
for(var/X in typesof(/datum/reagent/toxin))
var/datum/reagent/R = X
possible_reagents += initial(R.id)
/obj/item/organ/heart/gland/chem/activate()
var/chem_to_add = pick(possible_reagents)
owner.reagents.add_reagent(chem_to_add, 2)
owner.adjustToxLoss(-2, TRUE, TRUE)
..()
/obj/item/organ/heart/gland/plasma
cooldown_low = 1200
cooldown_high = 1800
uses = -1
mind_control_uses = 1
mind_control_duration = 800
/obj/item/organ/heart/gland/plasma/activate()
to_chat(owner, "<span class='warning'>You feel bloated.</span>")
addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, owner, "<span class='userdanger'>A massive stomachache overcomes you.</span>"), 150)
addtimer(CALLBACK(src, .proc/vomit_plasma), 200)
/obj/item/organ/heart/gland/plasma/proc/vomit_plasma()
if(!owner)
return
owner.visible_message("<span class='danger'>[owner] vomits a cloud of plasma!</span>")
var/turf/open/T = get_turf(owner)
if(istype(T))
T.atmos_spawn_air("plasma=50;TEMP=[T20C]")
owner.vomit()
@@ -0,0 +1,139 @@
/obj/machinery/computer/camera_advanced/abductor
name = "Human Observation Console"
var/team_number = 0
networks = list("ss13", "abductor")
var/datum/action/innate/teleport_in/tele_in_action = new
var/datum/action/innate/teleport_out/tele_out_action = new
var/datum/action/innate/teleport_self/tele_self_action = new
var/datum/action/innate/vest_mode_swap/vest_mode_action = new
var/datum/action/innate/vest_disguise_swap/vest_disguise_action = new
var/datum/action/innate/set_droppoint/set_droppoint_action = new
var/obj/machinery/abductor/console/console
lock_override = TRUE
icon = 'icons/obj/abductor.dmi'
icon_state = "camera"
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
/obj/machinery/computer/camera_advanced/abductor/CreateEye()
..()
eyeobj.visible_icon = 1
eyeobj.icon = 'icons/obj/abductor.dmi'
eyeobj.icon_state = "camera_target"
/obj/machinery/computer/camera_advanced/abductor/GrantActions(mob/living/carbon/user)
..()
if(tele_in_action)
tele_in_action.target = console.pad
tele_in_action.Grant(user)
actions += tele_in_action
if(tele_out_action)
tele_out_action.target = console
tele_out_action.Grant(user)
actions += tele_out_action
if(tele_self_action)
tele_self_action.target = console.pad
tele_self_action.Grant(user)
actions += tele_self_action
if(vest_mode_action)
vest_mode_action.target = console
vest_mode_action.Grant(user)
actions += vest_mode_action
if(vest_disguise_action)
vest_disguise_action.target = console
vest_disguise_action.Grant(user)
actions += vest_disguise_action
if(set_droppoint_action)
set_droppoint_action.target = console
set_droppoint_action.Grant(user)
actions += set_droppoint_action
/obj/machinery/computer/camera_advanced/abductor/proc/IsScientist(mob/living/carbon/human/H)
var/datum/species/abductor/S = H.dna.species
return S.scientist
/datum/action/innate/teleport_in
name = "Send To"
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "beam_down"
/datum/action/innate/teleport_in/Activate()
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/aiEye/remote/remote_eye = C.remote_control
var/obj/machinery/abductor/pad/P = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
P.PadToLoc(remote_eye.loc)
/datum/action/innate/teleport_out
name = "Retrieve"
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "beam_up"
/datum/action/innate/teleport_out/Activate()
if(!target || !iscarbon(owner))
return
var/obj/machinery/abductor/console/console = target
console.TeleporterRetrieve()
/datum/action/innate/teleport_self
name = "Send Self"
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "beam_down"
/datum/action/innate/teleport_self/Activate()
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/aiEye/remote/remote_eye = C.remote_control
var/obj/machinery/abductor/pad/P = target
if(GLOB.cameranet.checkTurfVis(remote_eye.loc))
P.MobToLoc(remote_eye.loc,C)
/datum/action/innate/vest_mode_swap
name = "Switch Vest Mode"
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "vest_mode"
/datum/action/innate/vest_mode_swap/Activate()
if(!target || !iscarbon(owner))
return
var/obj/machinery/abductor/console/console = target
console.FlipVest()
/datum/action/innate/vest_disguise_swap
name = "Switch Vest Disguise"
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "vest_disguise"
/datum/action/innate/vest_disguise_swap/Activate()
if(!target || !iscarbon(owner))
return
var/obj/machinery/abductor/console/console = target
console.SelectDisguise(remote=1)
/datum/action/innate/set_droppoint
name = "Set Experiment Release Point"
icon_icon = 'icons/mob/actions/actions_minor_antag.dmi'
button_icon_state = "set_drop"
/datum/action/innate/set_droppoint/Activate()
if(!target || !iscarbon(owner))
return
var/mob/living/carbon/human/C = owner
var/mob/camera/aiEye/remote/remote_eye = C.remote_control
var/obj/machinery/abductor/console/console = target
console.SetDroppoint(remote_eye.loc,owner)
@@ -0,0 +1,239 @@
/proc/get_abductor_console(team_number)
for(var/obj/machinery/abductor/console/C in GLOB.machines)
if(C.team_number == team_number)
return C
//Common
/obj/machinery/abductor
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/team_number = 0
//Console
/obj/machinery/abductor/console
name = "abductor console"
desc = "Ship command center."
icon = 'icons/obj/abductor.dmi'
icon_state = "console"
density = TRUE
var/obj/item/abductor/gizmo/gizmo
var/obj/item/clothing/suit/armor/abductor/vest/vest
var/obj/machinery/abductor/experiment/experiment
var/obj/machinery/abductor/pad/pad
var/obj/machinery/computer/camera_advanced/abductor/camera
var/list/datum/icon_snapshot/disguises = list()
/obj/machinery/abductor/console/attack_hand(mob/user)
. = ..()
if(.)
return
if(!isabductor(user))
to_chat(user, "<span class='warning'>You start mashing alien buttons at random!</span>")
if(do_after(user,100, target = src))
TeleporterSend()
return
user.set_machine(src)
var/dat = ""
dat += "<H3> Abductsoft 3000 </H3>"
if(experiment)
var/points = experiment.points
var/credits = experiment.credits
dat += "Collected Samples : [points] <br>"
dat += "Gear Credits: [credits] <br>"
dat += "<b>Transfer data in exchange for supplies:</b><br>"
dat += "<a href='?src=[REF(src)];dispense=baton'>Advanced Baton</A><br>"
dat += "<a href='?src=[REF(src)];dispense=helmet'>Agent Helmet</A><br>"
dat += "<a href='?src=[REF(src)];dispense=vest'>Agent Vest</A><br>"
dat += "<a href='?src=[REF(src)];dispense=silencer'>Radio Silencer</A><br>"
dat += "<a href='?src=[REF(src)];dispense=tool'>Science Tool</A><br>"
dat += "<a href='?src=[REF(src)];dispense=mind_device'>Mental Interface Device</A><br>"
else
dat += "<span class='bad'>NO EXPERIMENT MACHINE DETECTED</span> <br>"
if(pad)
dat += "<span class='bad'>Emergency Teleporter System.</span>"
dat += "<span class='bad'>Consider using primary observation console first.</span>"
dat += "<a href='?src=[REF(src)];teleporter_send=1'>Activate Teleporter</A><br>"
if(gizmo && gizmo.marked)
dat += "<a href='?src=[REF(src)];teleporter_retrieve=1'>Retrieve Mark</A><br>"
else
dat += "<span class='linkOff'>Retrieve Mark</span><br>"
else
dat += "<span class='bad'>NO TELEPAD DETECTED</span></br>"
if(vest)
dat += "<h4> Agent Vest Mode </h4><br>"
var/mode = vest.mode
if(mode == VEST_STEALTH)
dat += "<a href='?src=[REF(src)];flip_vest=1'>Combat</A>"
dat += "<span class='linkOff'>Stealth</span>"
else
dat += "<span class='linkOff'>Combat</span>"
dat += "<a href='?src=[REF(src)];flip_vest=1'>Stealth</A>"
dat+="<br>"
dat += "<a href='?src=[REF(src)];select_disguise=1'>Select Agent Vest Disguise</a><br>"
dat += "<a href='?src=[REF(src)];toggle_vest=1'>[HAS_TRAIT_FROM(vest, TRAIT_NODROP, ABDUCTOR_VEST_TRAIT) ? "Unlock" : "Lock"] Vest</a><br>"
else
dat += "<span class='bad'>NO AGENT VEST DETECTED</span>"
var/datum/browser/popup = new(user, "computer", "Abductor Console", 400, 500)
popup.set_content(dat)
popup.open()
/obj/machinery/abductor/console/Topic(href, href_list)
if(..())
return
usr.set_machine(src)
if(href_list["teleporter_send"])
TeleporterSend()
else if(href_list["teleporter_retrieve"])
TeleporterRetrieve()
else if(href_list["flip_vest"])
FlipVest()
else if(href_list["toggle_vest"])
if(vest)
vest.toggle_nodrop()
else if(href_list["select_disguise"])
SelectDisguise()
else if(href_list["dispense"])
switch(href_list["dispense"])
if("baton")
Dispense(/obj/item/abductor_baton,cost=2)
if("helmet")
Dispense(/obj/item/clothing/head/helmet/abductor)
if("silencer")
Dispense(/obj/item/abductor/silencer)
if("tool")
Dispense(/obj/item/abductor/gizmo)
if("vest")
Dispense(/obj/item/clothing/suit/armor/abductor/vest)
if("mind_device")
Dispense(/obj/item/abductor/mind_device,cost=2)
updateUsrDialog()
/obj/machinery/abductor/console/proc/TeleporterRetrieve()
if(pad && gizmo && gizmo.marked)
pad.Retrieve(gizmo.marked)
/obj/machinery/abductor/console/proc/TeleporterSend()
if(pad)
pad.Send()
/obj/machinery/abductor/console/proc/FlipVest()
if(vest)
vest.flip_mode()
/obj/machinery/abductor/console/proc/SelectDisguise(remote = FALSE)
var/list/disguises2 = list()
for(var/name in disguises)
var/datum/icon_snapshot/snap = disguises[name]
var/image/dummy = image(snap.icon, src, snap.icon_state)
dummy.overlays = snap.overlays
disguises2[name] = dummy
var/entry_name
if(remote)
entry_name = show_radial_menu(usr, camera.eyeobj, disguises2)
else
entry_name = show_radial_menu(usr, src, disguises2)
var/datum/icon_snapshot/chosen = disguises[entry_name]
if(chosen && vest && (remote || in_range(usr,src)))
vest.SetDisguise(chosen)
/obj/machinery/abductor/console/proc/SetDroppoint(turf/open/location,user)
if(!istype(location))
to_chat(user, "<span class='warning'>That place is not safe for the specimen.</span>")
return
if(pad)
pad.teleport_target = location
to_chat(user, "<span class='notice'>Location marked as test subject release point.</span>")
/obj/machinery/abductor/console/Initialize(mapload)
..()
return INITIALIZE_HINT_LATELOAD
/obj/machinery/abductor/console/LateInitialize()
if(!team_number)
return
for(var/obj/machinery/abductor/pad/p in GLOB.machines)
if(p.team_number == team_number)
pad = p
break
for(var/obj/machinery/abductor/experiment/e in GLOB.machines)
if(e.team_number == team_number)
experiment = e
e.console = src
for(var/obj/machinery/computer/camera_advanced/abductor/c in GLOB.machines)
if(c.team_number == team_number)
camera = c
c.console = src
/obj/machinery/abductor/console/proc/AddSnapshot(mob/living/carbon/human/target)
if(istype(target.get_item_by_slot(SLOT_HEAD), /obj/item/clothing/head/foilhat))
say("Subject wearing specialized protective headgear, unable to get a proper scan!")
return
var/datum/icon_snapshot/entry = new
entry.name = target.name
entry.icon = target.icon
entry.icon_state = target.icon_state
entry.overlays = target.get_overlays_copy(list(HANDS_LAYER)) //ugh
//Update old disguise instead of adding new one
if(disguises[entry.name])
disguises[entry.name] = entry
return
disguises[entry.name] = entry
/obj/machinery/abductor/console/proc/AddGizmo(obj/item/abductor/gizmo/G)
if(G == gizmo && G.console == src)
return FALSE
if(G.console)
G.console.gizmo = null
gizmo = G
G.console = src
return TRUE
/obj/machinery/abductor/console/proc/AddVest(obj/item/clothing/suit/armor/abductor/vest/V)
if(vest == V)
return FALSE
for(var/obj/machinery/abductor/console/C in GLOB.machines)
if(C.vest == V)
C.vest = null
break
vest = V
return TRUE
/obj/machinery/abductor/console/attackby(obj/O, mob/user, params)
if(istype(O, /obj/item/abductor/gizmo) && AddGizmo(O))
to_chat(user, "<span class='notice'>You link the tool to the console.</span>")
else if(istype(O, /obj/item/clothing/suit/armor/abductor/vest) && AddVest(O))
to_chat(user, "<span class='notice'>You link the vest to the console.</span>")
else
return ..()
/obj/machinery/abductor/console/proc/Dispense(item,cost=1)
if(experiment && experiment.credits >= cost)
experiment.credits -=cost
say("Incoming supply!")
var/drop_location = loc
if(pad)
flick("alien-pad", pad)
drop_location = pad.loc
new item(drop_location)
else
say("Insufficent data!")
@@ -0,0 +1,84 @@
/obj/machinery/abductor/gland_dispenser
name = "replacement organ storage"
desc = "A tank filled with replacement organs."
icon = 'icons/obj/abductor.dmi'
icon_state = "dispenser"
density = TRUE
var/list/gland_types
var/list/gland_colors
var/list/amounts
/obj/machinery/abductor/gland_dispenser/proc/random_color()
//TODO : replace with presets or spectrum
return rgb(rand(0,255),rand(0,255),rand(0,255))
/obj/machinery/abductor/gland_dispenser/Initialize()
. = ..()
gland_types = subtypesof(/obj/item/organ/heart/gland)
gland_types = shuffle(gland_types)
gland_colors = new/list(gland_types.len)
amounts = new/list(gland_types.len)
for(var/i=1,i<=gland_types.len,i++)
gland_colors[i] = random_color()
amounts[i] = rand(1,5)
/obj/machinery/abductor/gland_dispenser/attack_hand(mob/user)
. = ..()
if(.)
return
if(!isabductor(user))
return
user.set_machine(src)
var/box_css = {"
<style>
a.box.gland {
float: left;
width: 20px;
height: 20px;
margin: 5px;
border-width: 1px;
border-style: solid;
border-color: rgba(0,0,0,.2);
text-align: center;
}
</style>"}
var/dat = ""
var/item_count = 0
for(var/i=1,i<=gland_colors.len,i++)
item_count++
var/g_color = gland_colors[i]
var/amount = amounts[i]
dat += "<a class='box gland' style='background-color:[g_color]' href='?src=[REF(src)];dispense=[i]'>[amount]</a>"
if(item_count == 4) // Four boxes per line
dat +="</br></br>"
item_count = 0
var/datum/browser/popup = new(user, "glands", "Gland Dispenser", 200, 200)
popup.add_head_content(box_css)
popup.set_content(dat)
popup.open()
return
/obj/machinery/abductor/gland_dispenser/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/organ/heart/gland))
if(!user.transferItemToLoc(W, src))
return
for(var/i=1,i<=gland_colors.len,i++)
if(gland_types[i] == W.type)
amounts[i]++
else
return ..()
/obj/machinery/abductor/gland_dispenser/Topic(href, href_list)
if(..())
return
usr.set_machine(src)
if(href_list["dispense"])
Dispense(text2num(href_list["dispense"]))
updateUsrDialog()
/obj/machinery/abductor/gland_dispenser/proc/Dispense(count)
if(amounts[count]>0)
amounts[count]--
var/T = gland_types[count]
new T(get_turf(src))
@@ -0,0 +1,230 @@
/obj/machinery/abductor/experiment
name = "experimentation machine"
desc = "A large man-sized tube sporting a complex array of surgical machinery."
icon = 'icons/obj/abductor.dmi'
icon_state = "experiment-open"
density = FALSE
state_open = TRUE
var/points = 0
var/credits = 0
var/list/history
var/list/abductee_minds
var/flash = " - || - "
var/obj/machinery/abductor/console/console
var/message_cooldown = 0
var/breakout_time = 450
/obj/machinery/abductor/experiment/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !target.Adjacent(user) || !ishuman(target))
return
if(isabductor(target))
return
close_machine(target)
/obj/machinery/abductor/experiment/attack_hand(mob/user)
. = ..()
if(.)
return
experimentUI(user)
/obj/machinery/abductor/experiment/open_machine()
if(!state_open && !panel_open)
..()
/obj/machinery/abductor/experiment/close_machine(mob/target)
for(var/A in loc)
if(isabductor(A))
return
if(state_open && !panel_open)
..(target)
/obj/machinery/abductor/experiment/relaymove(mob/user)
if(user.stat != CONSCIOUS)
return
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
to_chat(user, "<span class='warning'>[src]'s door won't budge!</span>")
/obj/machinery/abductor/experiment/container_resist(mob/living/user)
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message("<span class='notice'>You see [user] kicking against the door of [src]!</span>", \
"<span class='notice'>You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)</span>", \
"<span class='italics'>You hear a metallic creaking from [src].</span>")
if(do_after(user,(breakout_time), target = src))
if(!user || user.stat != CONSCIOUS || user.loc != src || state_open)
return
user.visible_message("<span class='warning'>[user] successfully broke out of [src]!</span>", \
"<span class='notice'>You successfully break out of [src]!</span>")
open_machine()
/obj/machinery/abductor/experiment/proc/dissection_icon(mob/living/carbon/human/H)
var/icon/photo = null
var/g = (H.gender == FEMALE) ? "f" : "m"
if(H.dna.species.use_skintones)
photo = icon("icon" = 'icons/mob/human.dmi', "icon_state" = "[H.skin_tone]_[g]")
else
photo = icon("icon" = 'icons/mob/human.dmi', "icon_state" = "[H.dna.species.id]_[g]")
photo.Blend("#[H.dna.features["mcolor"]]", ICON_MULTIPLY)
var/icon/eyes
if(EYECOLOR in H.dna.species.species_traits)
eyes = icon("icon" = 'icons/mob/human_face.dmi', "icon_state" = "eyes")
eyes.Blend("#[H.eye_color]", ICON_MULTIPLY)
var/datum/sprite_accessory/S
S = GLOB.hair_styles_list[H.hair_style]
if(S && (HAIR in H.dna.species.species_traits))
var/icon/hair = icon("icon" = S.icon, "icon_state" = "[S.icon_state]")
hair.Blend("#[H.hair_color]", ICON_MULTIPLY)
eyes.Blend(hair, ICON_OVERLAY)
S = GLOB.facial_hair_styles_list[H.facial_hair_style]
if(S && (FACEHAIR in H.dna.species.species_traits))
var/icon/facial = icon("icon" = S.icon, "icon_state" = "[S.icon_state]")
facial.Blend("#[H.facial_hair_color]", ICON_MULTIPLY)
eyes.Blend(facial, ICON_OVERLAY)
if(eyes)
photo.Blend(eyes, ICON_OVERLAY)
var/icon/splat = icon("icon" = 'icons/mob/dam_mob.dmi',"icon_state" = "chest30")
photo.Blend(splat,ICON_OVERLAY)
return photo
/obj/machinery/abductor/experiment/proc/experimentUI(mob/user)
var/dat
dat += "<h3> Experiment </h3>"
if(occupant)
var/obj/item/photo/P = new
P.picture = new
P.picture.picture_image = icon(dissection_icon(occupant), dir = SOUTH)
user << browse_rsc(P.picture.picture_image, "dissection_img")
dat += "<table><tr><td>"
dat += "<img src=dissection_img height=80 width=80>" //Avert your eyes
dat += "</td><td>"
dat += "<a href='?src=[REF(src)];experiment=1'>Probe</a><br>"
dat += "<a href='?src=[REF(src)];experiment=2'>Dissect</a><br>"
dat += "<a href='?src=[REF(src)];experiment=3'>Analyze</a><br>"
dat += "</td></tr></table>"
else
dat += "<span class='linkOff'>Experiment </span>"
if(!occupant)
dat += "<h3>Machine Unoccupied</h3>"
else
dat += "<h3>Subject Status : </h3>"
dat += "[occupant.name] => "
var/mob/living/mob_occupant = occupant
switch(mob_occupant.stat)
if(CONSCIOUS)
dat += "<span class='good'>Conscious</span>"
if(UNCONSCIOUS)
dat += "<span class='average'>Unconscious</span>"
else // DEAD
dat += "<span class='bad'>Deceased</span>"
dat += "<br>"
dat += "[flash]"
dat += "<br>"
dat += "<a href='?src=[REF(src)];refresh=1'>Scan</a>"
dat += "<a href='?src=[REF(src)];[state_open ? "close=1'>Close</a>" : "open=1'>Open</a>"]"
var/datum/browser/popup = new(user, "experiment", "Probing Console", 300, 300)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.set_content(dat)
popup.open()
/obj/machinery/abductor/experiment/Topic(href, href_list)
if(..() || usr == occupant)
return
usr.set_machine(src)
if(href_list["refresh"])
updateUsrDialog()
return
if(href_list["open"])
open_machine()
return
if(href_list["close"])
close_machine()
return
if(occupant)
var/mob/living/mob_occupant = occupant
if(mob_occupant.stat != DEAD)
if(href_list["experiment"])
flash = Experiment(occupant,href_list["experiment"],usr)
updateUsrDialog()
add_fingerprint(usr)
/obj/machinery/abductor/experiment/proc/Experiment(mob/occupant,type,mob/user)
LAZYINITLIST(history)
var/mob/living/carbon/human/H = occupant
var/datum/antagonist/abductor/user_abductor = user.mind.has_antag_datum(/datum/antagonist/abductor)
if(!user_abductor)
return "<span class='bad'>Authorization failure. Contact mothership immidiately.</span>"
var/point_reward = 0
if(H in history)
return "<span class='bad'>Specimen already in database.</span>"
if(H.stat == DEAD)
say("Specimen deceased - please provide fresh sample.")
return "<span class='bad'>Specimen deceased.</span>"
var/obj/item/organ/heart/gland/GlandTest = locate() in H.internal_organs
if(!GlandTest)
say("Experimental dissection not detected!")
return "<span class='bad'>No glands detected!</span>"
if(H.mind != null && H.ckey != null)
LAZYINITLIST(abductee_minds)
LAZYADD(history, H)
LAZYADD(abductee_minds, H.mind)
say("Processing specimen...")
sleep(5)
switch(text2num(type))
if(1)
to_chat(H, "<span class='warning'>You feel violated.</span>")
if(2)
to_chat(H, "<span class='warning'>You feel yourself being sliced apart and put back together.</span>")
if(3)
to_chat(H, "<span class='warning'>You feel intensely watched.</span>")
sleep(5)
user_abductor.team.abductees += H.mind
H.mind.add_antag_datum(/datum/antagonist/abductee)
for(var/obj/item/organ/heart/gland/G in H.internal_organs)
G.Start()
point_reward++
if(point_reward > 0)
open_machine()
SendBack(H)
playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
points += point_reward
credits += point_reward
return "<span class='good'>Experiment successful! [point_reward] new data-points collected.</span>"
else
playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 1)
return "<span class='bad'>Experiment failed! No replacement organ detected.</span>"
else
say("Brain activity nonexistent - disposing sample...")
open_machine()
SendBack(H)
return "<span class='bad'>Specimen braindead - disposed.</span>"
return "<span class='bad'>ERROR</span>"
/obj/machinery/abductor/experiment/proc/SendBack(mob/living/carbon/human/H)
H.Sleeping(160)
H.uncuff()
if(console && console.pad && console.pad.teleport_target)
H.forceMove(console.pad.teleport_target)
return
//Area not chosen / It's not safe area - teleport to arrivals
SSjob.SendToLateJoin(H, FALSE)
return
/obj/machinery/abductor/experiment/update_icon()
if(state_open)
icon_state = "experiment-open"
else
icon_state = "experiment"
@@ -0,0 +1,56 @@
/obj/machinery/abductor/pad
name = "Alien Telepad"
desc = "Use this to transport to and from the humans' habitat."
icon = 'icons/obj/abductor.dmi'
icon_state = "alien-pad-idle"
var/turf/teleport_target
/obj/machinery/abductor/pad/proc/Warp(mob/living/target)
if(!target.buckled)
target.forceMove(get_turf(src))
/obj/machinery/abductor/pad/proc/Send()
if(teleport_target == null)
teleport_target = GLOB.teleportlocs[pick(GLOB.teleportlocs)]
flick("alien-pad", src)
for(var/mob/living/target in loc)
target.forceMove(teleport_target)
new /obj/effect/temp_visual/dir_setting/ninja(get_turf(target), target.dir)
to_chat(target, "<span class='warning'>The instability of the warp leaves you disoriented!</span>")
target.Stun(60)
/obj/machinery/abductor/pad/proc/Retrieve(mob/living/target)
flick("alien-pad", src)
new /obj/effect/temp_visual/dir_setting/ninja(get_turf(target), target.dir)
Warp(target)
/obj/machinery/abductor/pad/proc/doMobToLoc(place, atom/movable/target)
flick("alien-pad", src)
target.forceMove(place)
new /obj/effect/temp_visual/dir_setting/ninja(get_turf(target), target.dir)
/obj/machinery/abductor/pad/proc/MobToLoc(place,mob/living/target)
new /obj/effect/temp_visual/teleport_abductor(place)
addtimer(CALLBACK(src, .proc/doMobToLoc, place, target), 80)
/obj/machinery/abductor/pad/proc/doPadToLoc(place)
flick("alien-pad", src)
for(var/mob/living/target in get_turf(src))
target.forceMove(place)
new /obj/effect/temp_visual/dir_setting/ninja(get_turf(target), target.dir)
/obj/machinery/abductor/pad/proc/PadToLoc(place)
new /obj/effect/temp_visual/teleport_abductor(place)
addtimer(CALLBACK(src, .proc/doPadToLoc, place), 80)
/obj/effect/temp_visual/teleport_abductor
name = "Huh"
icon = 'icons/obj/abductor.dmi'
icon_state = "teleport"
duration = 80
/obj/effect/temp_visual/teleport_abductor/Initialize()
. = ..()
var/datum/effect_system/spark_spread/S = new
S.set_up(10,0,loc)
S.start()
+67
View File
@@ -0,0 +1,67 @@
/datum/antagonist/blob
name = "Blob"
roundend_category = "blobs"
antagpanel_category = "Blob"
job_rank = ROLE_BLOB
var/datum/action/innate/blobpop/pop_action
var/starting_points_human_blob = 60
var/point_rate_human_blob = 2
/datum/antagonist/blob/roundend_report()
var/basic_report = ..()
//Display max blobpoints for blebs that lost
if(isovermind(owner.current)) //embarrasing if not
var/mob/camera/blob/overmind = owner.current
if(!overmind.victory_in_progress) //if it won this doesn't really matter
var/point_report = "<br><b>[owner.name]</b> took over [overmind.max_count] tiles at the height of its growth."
return basic_report+point_report
return basic_report
/datum/antagonist/blob/greet()
if(!isovermind(owner.current))
to_chat(owner,"<span class='userdanger'>You feel bloated.</span>")
/datum/antagonist/blob/on_gain()
create_objectives()
. = ..()
/datum/antagonist/blob/proc/create_objectives()
var/datum/objective/blob_takeover/main = new
main.owner = owner
objectives += main
owner.objectives |= objectives
/datum/antagonist/blob/apply_innate_effects(mob/living/mob_override)
if(!isovermind(owner.current))
if(!pop_action)
pop_action = new
pop_action.Grant(owner.current)
/datum/objective/blob_takeover
explanation_text = "Reach critical mass!"
//Non-overminds get this on blob antag assignment
/datum/action/innate/blobpop
name = "Pop"
desc = "Unleash the blob"
icon_icon = 'icons/mob/blob.dmi'
button_icon_state = "blob"
/datum/action/innate/blobpop/Activate()
var/mob/old_body = owner
var/datum/antagonist/blob/blobtag = owner.mind.has_antag_datum(/datum/antagonist/blob)
if(!blobtag)
Remove()
return
var/mob/camera/blob/B = new /mob/camera/blob(get_turf(old_body), blobtag.starting_points_human_blob)
owner.mind.transfer_to(B)
old_body.gib()
B.place_blob_core(blobtag.point_rate_human_blob, pop_override = TRUE)
/datum/antagonist/blob/antag_listing_status()
. = ..()
if(owner && owner.current)
var/mob/camera/blob/B = owner.current
if(istype(B))
. += "(Progress: [B.blobs_legit.len]/[B.blobwincount])"
@@ -0,0 +1,62 @@
/datum/station_state
var/floor = 0
var/wall = 0
var/r_wall = 0
var/window = 0
var/door = 0
var/grille = 0
var/mach = 0
/datum/station_state/proc/count()
for(var/Z in SSmapping.levels_by_trait(ZTRAIT_STATION))
for(var/turf/T in block(locate(1,1,Z), locate(world.maxx,world.maxy,Z)))
// don't count shuttles since they may have just left
if(istype(T.loc, /area/shuttle))
continue
if(isfloorturf(T))
var/turf/open/floor/TF = T
if(!(TF.burnt))
floor += 12
else
floor += 1
if(iswallturf(T))
var/turf/closed/wall/TW = T
if(TW.intact)
wall += 2
else
wall += 1
if(istype(T, /turf/closed/wall/r_wall))
var/turf/closed/wall/r_wall/TRW = T
if(TRW.intact)
r_wall += 2
else
r_wall += 1
for(var/obj/O in T.contents)
if(istype(O, /obj/structure/window))
window += 1
else if(istype(O, /obj/structure/grille))
var/obj/structure/grille/GR = O
if(!GR.broken)
grille += 1
else if(istype(O, /obj/machinery/door))
door += 1
else if(ismachinery(O))
mach += 1
/datum/station_state/proc/score(datum/station_state/result)
if(!result)
return 0
var/output = 0
output += (result.floor / max(floor,1))
output += (result.r_wall/ max(r_wall,1))
output += (result.wall / max(wall,1))
output += (result.window / max(window,1))
output += (result.door / max(door,1))
output += (result.grille / max(grille,1))
output += (result.mach / max(mach,1))
return (output/7)
@@ -0,0 +1,296 @@
////////////////
// BASE TYPE //
////////////////
//Do not spawn
/mob/living/simple_animal/hostile/blob
icon = 'icons/mob/blob.dmi'
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
bubble_icon = "blob"
speak_emote = null //so we use verb_yell/verb_say/etc
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
maxbodytemp = 360
unique_name = 1
a_intent = INTENT_HARM
var/mob/camera/blob/overmind = null
var/obj/structure/blob/factory/factory = null
/mob/living/simple_animal/hostile/blob/update_icons()
if(overmind)
add_atom_colour(overmind.blob_reagent_datum.color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
/mob/living/simple_animal/hostile/blob/Destroy()
if(overmind)
overmind.blob_mobs -= src
return ..()
/mob/living/simple_animal/hostile/blob/blob_act(obj/structure/blob/B)
if(stat != DEAD && health < maxHealth)
for(var/i in 1 to 2)
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
else
H.color = "#000000"
adjustHealth(-maxHealth*0.0125)
/mob/living/simple_animal/hostile/blob/fire_act(exposed_temperature, exposed_volume)
..()
if(exposed_temperature)
adjustFireLoss(CLAMP(0.01 * exposed_temperature, 1, 5))
else
adjustFireLoss(5)
/mob/living/simple_animal/hostile/blob/CanPass(atom/movable/mover, turf/target)
if(istype(mover, /obj/structure/blob))
return 1
return ..()
/mob/living/simple_animal/hostile/blob/Process_Spacemove(movement_dir = 0)
for(var/obj/structure/blob/B in range(1, src))
return 1
return ..()
/mob/living/simple_animal/hostile/blob/proc/blob_chat(msg)
var/spanned_message = say_quote(msg)
var/rendered = "<font color=\"#EE4000\"><b>\[Blob Telepathy\] [real_name]</b> [spanned_message]</font>"
for(var/M in GLOB.mob_list)
if(isovermind(M) || istype(M, /mob/living/simple_animal/hostile/blob))
to_chat(M, rendered)
if(isobserver(M))
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
////////////////
// BLOB SPORE //
////////////////
/mob/living/simple_animal/hostile/blob/blobspore
name = "blob spore"
desc = "A floating, fragile spore."
icon_state = "blobpod"
icon_living = "blobpod"
health = 30
maxHealth = 30
verb_say = "psychically pulses"
verb_ask = "psychically probes"
verb_exclaim = "psychically yells"
verb_yell = "psychically screams"
melee_damage_lower = 2
melee_damage_upper = 4
obj_damage = 20
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
attacktext = "hits"
attack_sound = 'sound/weapons/genhit1.ogg'
movement_type = FLYING
del_on_death = 1
deathmessage = "explodes into a cloud of gas!"
var/death_cloud_size = 1 //size of cloud produced from a dying spore
var/mob/living/carbon/human/oldguy
var/is_zombie = 0
gold_core_spawnable = HOSTILE_SPAWN
/mob/living/simple_animal/hostile/blob/blobspore/Initialize(mapload, var/obj/structure/blob/factory/linked_node)
if(istype(linked_node))
factory = linked_node
factory.spores += src
. = ..()
/mob/living/simple_animal/hostile/blob/blobspore/Life()
if(!is_zombie && isturf(src.loc))
for(var/mob/living/carbon/human/H in view(src,1)) //Only for corpse right next to/on same tile
if(H.stat == DEAD)
Zombify(H)
break
if(factory && z != factory.z)
death()
..()
/mob/living/simple_animal/hostile/blob/blobspore/proc/Zombify(mob/living/carbon/human/H)
is_zombie = 1
if(H.wear_suit)
var/obj/item/clothing/suit/armor/A = H.wear_suit
maxHealth += A.armor.melee //That zombie's got armor, I want armor!
maxHealth += 40
health = maxHealth
name = "blob zombie"
desc = "A shambling corpse animated by the blob."
mob_biotypes += MOB_HUMANOID
melee_damage_lower += 8
melee_damage_upper += 11
movement_type = GROUND
death_cloud_size = 0
icon = H.icon
icon_state = "zombie"
H.hair_style = null
H.update_hair()
H.forceMove(src)
oldguy = H
update_icons()
visible_message("<span class='warning'>The corpse of [H.name] suddenly rises!</span>")
/mob/living/simple_animal/hostile/blob/blobspore/death(gibbed)
// On death, create a small smoke of harmful gas (s-Acid)
var/datum/effect_system/smoke_spread/chem/S = new
var/turf/location = get_turf(src)
// Create the reagents to put into the air
create_reagents(10)
if(overmind && overmind.blob_reagent_datum)
reagents.add_reagent(overmind.blob_reagent_datum.id, 10)
else
reagents.add_reagent("spore", 10)
// Attach the smoke spreader and setup/start it.
S.attach(location)
S.set_up(reagents, death_cloud_size, location, silent = TRUE)
S.start()
if(factory)
factory.spore_delay = world.time + factory.spore_cooldown //put the factory on cooldown
..()
/mob/living/simple_animal/hostile/blob/blobspore/Destroy()
if(factory)
factory.spores -= src
factory = null
if(oldguy)
oldguy.forceMove(get_turf(src))
oldguy = null
return ..()
/mob/living/simple_animal/hostile/blob/blobspore/update_icons()
if(overmind)
add_atom_colour(overmind.blob_reagent_datum.complementary_color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
if(is_zombie)
copy_overlays(oldguy, TRUE)
var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/blob.dmi', "blob_head")
if(overmind)
blob_head_overlay.color = overmind.blob_reagent_datum.complementary_color
color = initial(color)//looks better.
add_overlay(blob_head_overlay)
/mob/living/simple_animal/hostile/blob/blobspore/weak
name = "fragile blob spore"
health = 15
maxHealth = 15
melee_damage_lower = 1
melee_damage_upper = 2
death_cloud_size = 0
/////////////////
// BLOBBERNAUT //
/////////////////
/mob/living/simple_animal/hostile/blob/blobbernaut
name = "blobbernaut"
desc = "A hulking, mobile chunk of blobmass."
icon_state = "blobbernaut"
icon_living = "blobbernaut"
icon_dead = "blobbernaut_dead"
health = 200
maxHealth = 200
damage_coeff = list(BRUTE = 0.5, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
melee_damage_lower = 20
melee_damage_upper = 20
obj_damage = 60
attacktext = "slams"
attack_sound = 'sound/effects/blobattack.ogg'
verb_say = "gurgles"
verb_ask = "demands"
verb_exclaim = "roars"
verb_yell = "bellows"
force_threshold = 10
pressure_resistance = 50
mob_size = MOB_SIZE_LARGE
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
hud_type = /datum/hud/blobbernaut
var/independent = FALSE
/mob/living/simple_animal/hostile/blob/blobbernaut/Initialize()
. = ..()
if(!independent) //no pulling people deep into the blob
verbs -= /mob/living/verb/pulled
else
pass_flags &= ~PASSBLOB
/mob/living/simple_animal/hostile/blob/blobbernaut/Life()
if(..())
var/list/blobs_in_area = range(2, src)
if(independent)
return // strong independent blobbernaut that don't need no blob
var/damagesources = 0
if(!(locate(/obj/structure/blob) in blobs_in_area))
damagesources++
if(!factory)
damagesources++
else
if(locate(/obj/structure/blob/core) in blobs_in_area)
adjustHealth(-maxHealth*0.1)
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
else
H.color = "#000000"
if(locate(/obj/structure/blob/node) in blobs_in_area)
adjustHealth(-maxHealth*0.05)
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src))
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
else
H.color = "#000000"
if(damagesources)
for(var/i in 1 to damagesources)
adjustHealth(maxHealth*0.025) //take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both
var/image/I = new('icons/mob/blob.dmi', src, "nautdamage", MOB_LAYER+0.01)
I.appearance_flags = RESET_COLOR
if(overmind)
I.color = overmind.blob_reagent_datum.complementary_color
flick_overlay_view(I, src, 8)
/mob/living/simple_animal/hostile/blob/blobbernaut/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
. = ..()
if(updating_health)
update_health_hud()
/mob/living/simple_animal/hostile/blob/blobbernaut/update_health_hud()
if(hud_used)
hud_used.healths.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#e36600'>[round((health / maxHealth) * 100, 0.5)]%</font></div>"
/mob/living/simple_animal/hostile/blob/blobbernaut/AttackingTarget()
. = ..()
if(. && isliving(target) && overmind)
var/mob/living/L = target
var/mob_protection = L.get_permeability_protection()
overmind.blob_reagent_datum.reaction_mob(L, VAPOR, 20, 0, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
/mob/living/simple_animal/hostile/blob/blobbernaut/update_icons()
..()
if(overmind) //if we have an overmind, we're doing chemical reactions instead of pure damage
melee_damage_lower = 4
melee_damage_upper = 4
attacktext = overmind.blob_reagent_datum.blobbernaut_message
else
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
attacktext = initial(attacktext)
/mob/living/simple_animal/hostile/blob/blobbernaut/death(gibbed)
..(gibbed)
if(factory)
factory.naut = null //remove this naut from its factory
factory.max_integrity = initial(factory.max_integrity)
flick("blobbernaut_death", src)
/mob/living/simple_animal/hostile/blob/blobbernaut/independent
independent = TRUE
gold_core_spawnable = HOSTILE_SPAWN
@@ -0,0 +1,88 @@
/obj/structure/blob/core
name = "blob core"
icon = 'icons/mob/blob.dmi'
icon_state = "blank_blob"
desc = "A huge, pulsating yellow mass."
max_integrity = 400
armor = list("melee" = 30, "bullet" = 30, "laser" = 20, "energy" = 5, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 75, "acid" = 90) // Last stand
explosion_block = 6
point_return = -1
health_regen = 0 //we regen in Life() instead of when pulsed
var/core_regen = 2
var/resource_delay = 0
var/point_rate = 2
/obj/structure/blob/core/Initialize(mapload, client/new_overmind = null, new_rate = 2, placed = 0)
GLOB.blob_cores += src
START_PROCESSING(SSobj, src)
GLOB.poi_list |= src
update_icon() //so it atleast appears
if(!placed && !overmind)
return INITIALIZE_HINT_QDEL
if(overmind)
update_icon()
point_rate = new_rate
addtimer(CALLBACK(src, .proc/generate_announcement), 1800)
. = ..()
/obj/structure/blob/core/proc/generate_announcement()
priority_announce("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", 'sound/ai/outbreak5.ogg')
/obj/structure/blob/core/scannerreport()
return "Directs the blob's expansion, gradually expands, and sustains nearby blob spores and blobbernauts."
/obj/structure/blob/core/update_icon()
cut_overlays()
color = null
var/mutable_appearance/blob_overlay = mutable_appearance('icons/mob/blob.dmi', "blob")
if(overmind)
blob_overlay.color = overmind.blob_reagent_datum.color
add_overlay(blob_overlay)
add_overlay(mutable_appearance('icons/mob/blob.dmi', "blob_core_overlay"))
/obj/structure/blob/core/Destroy()
GLOB.blob_cores -= src
if(overmind)
overmind.blob_core = null
overmind = null
STOP_PROCESSING(SSobj, src)
GLOB.poi_list -= src
return ..()
/obj/structure/blob/core/ex_act(severity, target)
var/damage = 50 - 10 * severity //remember, the core takes half brute damage, so this is 20/15/10 damage based on severity
take_damage(damage, BRUTE, "bomb", 0)
/obj/structure/blob/core/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, overmind_reagent_trigger = 1)
. = ..()
if(obj_integrity > 0)
if(overmind) //we should have an overmind, but...
overmind.update_health_hud()
/obj/structure/blob/core/Life()
if(QDELETED(src))
return
if(!overmind)
qdel(src)
else
if(resource_delay <= world.time)
resource_delay = world.time + 10 // 1 second
overmind.add_points(point_rate)
obj_integrity = min(max_integrity, obj_integrity+core_regen)
if(overmind)
overmind.update_health_hud()
Pulse_Area(overmind, 12, 4, 3)
for(var/obj/structure/blob/normal/B in range(1, src))
if(prob(5))
B.change_to(/obj/structure/blob/shield/core, overmind)
..()
/obj/structure/blob/core/ComponentInitialize()
. = ..()
AddComponent(/datum/component/stationloving, FALSE, TRUE)
/obj/structure/blob/core/onTransitZ(old_z, new_z)
if(overmind && is_station_level(new_z))
overmind.forceMove(get_turf(src))
return ..()
@@ -0,0 +1,47 @@
/obj/structure/blob/factory
name = "factory blob"
icon = 'icons/mob/blob.dmi'
icon_state = "blob_factory"
desc = "A thick spire of tendrils."
max_integrity = 200
health_regen = 1
point_return = 25
armor = list("melee" = 10, "bullet" = 20, "laser" = 15, "energy" = 10, "bomb" = 40, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
var/list/spores = list()
var/mob/living/simple_animal/hostile/blob/blobbernaut/naut = null
var/max_spores = 3
var/spore_delay = 0
var/spore_cooldown = 80 //8 seconds between spores and after spore death
/obj/structure/blob/factory/scannerreport()
if(naut)
return "It is currently sustaining a blobbernaut, making it fragile and unable to produce blob spores."
return "Will produce a blob spore every few seconds."
/obj/structure/blob/factory/Destroy()
for(var/mob/living/simple_animal/hostile/blob/blobspore/spore in spores)
if(spore.factory == src)
spore.factory = null
if(naut)
naut.factory = null
to_chat(naut, "<span class='userdanger'>Your factory was destroyed! You feel yourself dying!</span>")
naut.throw_alert("nofactory", /obj/screen/alert/nofactory)
spores = null
return ..()
/obj/structure/blob/factory/Be_Pulsed()
. = ..()
if(naut)
return
if(spores.len >= max_spores)
return
if(spore_delay > world.time)
return
flick("blob_factory_glow", src)
spore_delay = world.time + spore_cooldown
var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore(src.loc, src)
if(overmind) //if we don't have an overmind, we don't need to do anything but make a spore
BS.overmind = overmind
BS.update_icons()
overmind.blob_mobs.Add(BS)
@@ -0,0 +1,36 @@
/obj/structure/blob/node
name = "blob node"
icon = 'icons/mob/blob.dmi'
icon_state = "blank_blob"
desc = "A large, pulsating yellow mass."
max_integrity = 200
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 65, "acid" = 90)
health_regen = 3
point_return = 25
/obj/structure/blob/node/Initialize()
GLOB.blob_nodes += src
START_PROCESSING(SSobj, src)
. = ..()
/obj/structure/blob/node/scannerreport()
return "Gradually expands and sustains nearby blob spores and blobbernauts."
/obj/structure/blob/node/update_icon()
cut_overlays()
color = null
var/mutable_appearance/blob_overlay = mutable_appearance('icons/mob/blob.dmi', "blob")
if(overmind)
blob_overlay.color = overmind.blob_reagent_datum.color
add_overlay(blob_overlay)
add_overlay(mutable_appearance('icons/mob/blob.dmi', "blob_node_overlay"))
/obj/structure/blob/node/Destroy()
GLOB.blob_nodes -= src
STOP_PROCESSING(SSobj, src)
return ..()
/obj/structure/blob/node/Life()
if(overmind)
Pulse_Area(overmind, 10, 3, 2)
@@ -0,0 +1,32 @@
/obj/structure/blob/resource
name = "resource blob"
icon = 'icons/mob/blob.dmi'
icon_state = "blob_resource"
desc = "A thin spire of slightly swaying tendrils."
max_integrity = 60
point_return = 15
armor = list("melee" = 10, "bullet" = 10, "laser" = 0, "energy" = 0, "bomb" = 15, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
var/resource_delay = 0
/obj/structure/blob/resource/scannerreport()
return "Gradually supplies the blob with resources, increasing the rate of expansion."
/obj/structure/blob/resource/creation_action()
if(overmind)
overmind.resource_blobs += src
/obj/structure/blob/resource/Destroy()
if(overmind)
overmind.resource_blobs -= src
return ..()
/obj/structure/blob/resource/Be_Pulsed()
. = ..()
if(resource_delay > world.time)
return
flick("blob_resource_glow", src)
if(overmind)
overmind.add_points(1)
resource_delay = world.time + 40 + overmind.resource_blobs.len * 2.5 //4 seconds plus a quarter second for each resource blob the overmind has
else
resource_delay = world.time + 40
@@ -0,0 +1,40 @@
/obj/structure/blob/shield
name = "strong blob"
icon = 'icons/mob/blob.dmi'
icon_state = "blob_shield"
desc = "A solid wall of slightly twitching tendrils."
max_integrity = 150
brute_resist = 0.25
explosion_block = 3
point_return = 4
atmosblock = TRUE
armor = list("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
var/weakened
/obj/structure/blob/shield/scannerreport()
if(atmosblock)
return "Will prevent the spread of atmospheric changes."
return "N/A"
/obj/structure/blob/shield/core
point_return = 0
/obj/structure/blob/shield/update_icon()
..()
if(obj_integrity <= 70)
icon_state = "blob_shield_damaged"
name = "weakened strong blob"
desc = "A wall of twitching tendrils."
atmosblock = FALSE
if(!weakened)
armor = armor.setRating("melee" = 15, "bullet" = 15, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
weakened = TRUE
else
icon_state = initial(icon_state)
name = initial(name)
desc = initial(desc)
atmosblock = TRUE
if(weakened)
armor = armor.setRating("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
weakened = FALSE
air_update_turf(1)
@@ -0,0 +1,258 @@
//Few global vars to track the blob
GLOBAL_LIST_EMPTY(blobs) //complete list of all blobs made.
GLOBAL_LIST_EMPTY(blob_cores)
GLOBAL_LIST_EMPTY(overminds)
GLOBAL_LIST_EMPTY(blob_nodes)
/mob/camera/blob
name = "Blob Overmind"
real_name = "Blob Overmind"
desc = "The overmind. It controls the blob."
icon = 'icons/mob/blob.dmi'
icon_state = "marker"
mouse_opacity = MOUSE_OPACITY_ICON
move_on_shuttle = 1
see_in_dark = 8
invisibility = INVISIBILITY_OBSERVER
layer = FLY_LAYER
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
call_life = TRUE
hud_type = /datum/hud/blob_overmind
var/obj/structure/blob/core/blob_core = null // The blob overmind's core
var/blob_points = 0
var/max_blob_points = 100
var/last_attack = 0
var/datum/reagent/blob/blob_reagent_datum = new/datum/reagent/blob()
var/list/blob_mobs = list()
var/list/resource_blobs = list()
var/free_chem_rerolls = 1 //one free chemical reroll
var/nodes_required = 1 //if the blob needs nodes to place resource and factory blobs
var/placed = 0
var/base_point_rate = 2 //for blob core placement
var/manualplace_min_time = 600 //in deciseconds //a minute, to get bearings
var/autoplace_max_time = 3600 //six minutes, as long as should be needed
var/list/blobs_legit = list()
var/max_count = 0 //The biggest it got before death
var/blobwincount = 400
var/victory_in_progress = FALSE
/mob/camera/blob/Initialize(mapload, starting_points = 60)
validate_location()
blob_points = starting_points
manualplace_min_time += world.time
autoplace_max_time += world.time
GLOB.overminds += src
var/new_name = "[initial(name)] ([rand(1, 999)])"
name = new_name
real_name = new_name
last_attack = world.time
var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob)))
blob_reagent_datum = new BC
color = blob_reagent_datum.complementary_color
if(blob_core)
blob_core.update_icon()
SSshuttle.registerHostileEnvironment(src)
.= ..()
/mob/camera/blob/proc/validate_location()
var/turf/T = get_turf(src)
if(!is_valid_turf(T) && LAZYLEN(GLOB.blobstart))
var/list/blobstarts = shuffle(GLOB.blobstart)
for(var/_T in blobstarts)
if(is_valid_turf(_T))
T = _T
break
if(!T)
CRASH("No blobspawnpoints and blob spawned in nullspace.")
forceMove(T)
/mob/camera/blob/proc/is_valid_turf(turf/T)
var/area/A = get_area(T)
if((A && !A.blob_allowed) || !T || !is_station_level(T.z) || isspaceturf(T))
return FALSE
return TRUE
/mob/camera/blob/Life()
if(!blob_core)
if(!placed)
if(manualplace_min_time && world.time >= manualplace_min_time)
to_chat(src, "<b><span class='big'><font color=\"#EE4000\">You may now place your blob core.</font></span></b>")
to_chat(src, "<span class='big'><font color=\"#EE4000\">You will automatically place your blob core in [DisplayTimeText(autoplace_max_time - world.time)].</font></span>")
manualplace_min_time = 0
if(autoplace_max_time && world.time >= autoplace_max_time)
place_blob_core(base_point_rate, 1)
else
qdel(src)
else if(!victory_in_progress && (blobs_legit.len >= blobwincount))
victory_in_progress = TRUE
priority_announce("Biohazard has reached critical mass. Station loss is imminent.", "Biohazard Alert")
set_security_level("delta")
max_blob_points = INFINITY
blob_points = INFINITY
addtimer(CALLBACK(src, .proc/victory), 450)
if(!victory_in_progress && max_count < blobs_legit.len)
max_count = blobs_legit.len
..()
/mob/camera/blob/proc/victory()
sound_to_playing_players('sound/machines/alarm.ogg')
sleep(100)
for(var/i in GLOB.mob_living_list)
var/mob/living/L = i
var/turf/T = get_turf(L)
if(!T || !is_station_level(T.z))
continue
if(L in GLOB.overminds || (L.pass_flags & PASSBLOB))
continue
var/area/Ablob = get_area(T)
if(!Ablob.blob_allowed)
continue
if(!(ROLE_BLOB in L.faction))
playsound(L, 'sound/effects/splat.ogg', 50, 1)
L.death()
new/mob/living/simple_animal/hostile/blob/blobspore(T)
else
L.fully_heal()
for(var/area/A in GLOB.sortedAreas)
if(!(A.type in GLOB.the_station_areas))
continue
if(!A.blob_allowed)
continue
A.color = blob_reagent_datum.color
A.name = "blob"
A.icon = 'icons/mob/blob.dmi'
A.icon_state = "blob_shield"
A.layer = BELOW_MOB_LAYER
A.invisibility = 0
A.blend_mode = 0
var/datum/antagonist/blob/B = mind.has_antag_datum(/datum/antagonist/blob)
if(B)
var/datum/objective/blob_takeover/main_objective = locate() in B.objectives
if(main_objective)
main_objective.completed = TRUE
to_chat(world, "<B>[real_name] consumed the station in an unstoppable tide!</B>")
SSticker.news_report = BLOB_WIN
SSticker.force_ending = 1
/mob/camera/blob/Destroy()
for(var/BL in GLOB.blobs)
var/obj/structure/blob/B = BL
if(B && B.overmind == src)
B.overmind = null
B.update_icon() //reset anything that was ours
for(var/BLO in blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
if(BM)
BM.overmind = null
BM.update_icons()
GLOB.overminds -= src
SSshuttle.clearHostileEnvironment(src)
return ..()
/mob/camera/blob/Login()
..()
to_chat(src, "<span class='notice'>You are the overmind!</span>")
blob_help()
update_health_hud()
add_points(0)
/mob/camera/blob/examine(mob/user)
..()
if(blob_reagent_datum)
to_chat(user, "Its chemical is <font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</font>.")
/mob/camera/blob/update_health_hud()
if(blob_core)
hud_used.healths.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#e36600'>[round(blob_core.obj_integrity)]</font></div>"
for(var/mob/living/simple_animal/hostile/blob/blobbernaut/B in blob_mobs)
if(B.hud_used && B.hud_used.blobpwrdisplay)
B.hud_used.blobpwrdisplay.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#82ed00'>[round(blob_core.obj_integrity)]</font></div>"
/mob/camera/blob/proc/add_points(points)
blob_points = CLAMP(blob_points + points, 0, max_blob_points)
hud_used.blobpwrdisplay.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#82ed00'>[round(blob_points)]</font></div>"
/mob/camera/blob/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
if (!message)
return
if (src.client)
if(client.prefs.muted & MUTE_IC)
to_chat(src, "You cannot send IC messages (muted).")
return
if (src.client.handle_spam_prevention(message,MUTE_IC))
return
if (stat)
return
blob_talk(message)
/mob/camera/blob/proc/blob_talk(message)
message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN))
if (!message)
return
src.log_talk(message, LOG_SAY)
var/message_a = say_quote(message)
var/rendered = "<span class='big'><font color=\"#EE4000\"><b>\[Blob Telepathy\] [name](<font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</font>)</b> [message_a]</font></span>"
for(var/mob/M in GLOB.mob_list)
if(isovermind(M) || istype(M, /mob/living/simple_animal/hostile/blob))
to_chat(M, rendered)
if(isobserver(M))
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
/mob/camera/blob/blob_act(obj/structure/blob/B)
return
/mob/camera/blob/Stat()
..()
if(statpanel("Status"))
if(blob_core)
stat(null, "Core Health: [blob_core.obj_integrity]")
stat(null, "Power Stored: [blob_points]/[max_blob_points]")
stat(null, "Blobs to Win: [blobs_legit.len]/[blobwincount]")
if(free_chem_rerolls)
stat(null, "You have [free_chem_rerolls] Free Chemical Reroll\s Remaining")
if(!placed)
if(manualplace_min_time)
stat(null, "Time Before Manual Placement: [max(round((manualplace_min_time - world.time)*0.1, 0.1), 0)]")
stat(null, "Time Before Automatic Placement: [max(round((autoplace_max_time - world.time)*0.1, 0.1), 0)]")
/mob/camera/blob/Move(NewLoc, Dir = 0)
if(placed)
var/obj/structure/blob/B = locate() in range("3x3", NewLoc)
if(B)
forceMove(NewLoc)
else
return 0
else
var/area/A = get_area(NewLoc)
if(isspaceturf(NewLoc) || istype(A, /area/shuttle)) //if unplaced, can't go on shuttles or space tiles
return 0
forceMove(NewLoc)
return 1
/mob/camera/blob/mind_initialize()
. = ..()
var/datum/antagonist/blob/B = mind.has_antag_datum(/datum/antagonist/blob)
if(!B)
mind.add_antag_datum(/datum/antagonist/blob)
@@ -0,0 +1,372 @@
/mob/camera/blob/proc/can_buy(cost = 15)
if(blob_points < cost)
to_chat(src, "<span class='warning'>You cannot afford this, you need at least [cost] resources!</span>")
return 0
add_points(-cost)
return 1
// Power verbs
/mob/camera/blob/proc/place_blob_core(point_rate, placement_override , pop_override = FALSE)
if(placed && placement_override != -1)
return 1
if(!placement_override)
if(!pop_override)
for(var/mob/living/M in range(7, src))
if(ROLE_BLOB in M.faction)
continue
if(M.client)
to_chat(src, "<span class='warning'>There is someone too close to place your blob core!</span>")
return 0
for(var/mob/living/M in view(13, src))
if(ROLE_BLOB in M.faction)
continue
if(M.client)
to_chat(src, "<span class='warning'>Someone could see your blob core from here!</span>")
return 0
var/turf/T = get_turf(src)
if(T.density)
to_chat(src, "<span class='warning'>This spot is too dense to place a blob core on!</span>")
return 0
for(var/obj/O in T)
if(istype(O, /obj/structure/blob))
if(istype(O, /obj/structure/blob/normal))
qdel(O)
else
to_chat(src, "<span class='warning'>There is already a blob here!</span>")
return 0
else if(O.density)
to_chat(src, "<span class='warning'>This spot is too dense to place a blob core on!</span>")
return 0
if(!pop_override && world.time <= manualplace_min_time && world.time <= autoplace_max_time)
to_chat(src, "<span class='warning'>It is too early to place your blob core!</span>")
return 0
else if(placement_override == 1)
var/turf/T = pick(GLOB.blobstart)
forceMove(T) //got overrided? you're somewhere random, motherfucker
if(placed && blob_core)
blob_core.forceMove(loc)
else
var/obj/structure/blob/core/core = new(get_turf(src), src, point_rate, 1)
core.overmind = src
blobs_legit += src
blob_core = core
core.update_icon()
update_health_hud()
placed = 1
return 1
/mob/camera/blob/verb/transport_core()
set category = "Blob"
set name = "Jump to Core"
set desc = "Move your camera to your core."
if(blob_core)
forceMove(blob_core.drop_location())
/mob/camera/blob/verb/jump_to_node()
set category = "Blob"
set name = "Jump to Node"
set desc = "Move your camera to a selected node."
if(GLOB.blob_nodes.len)
var/list/nodes = list()
for(var/i in 1 to GLOB.blob_nodes.len)
var/obj/structure/blob/node/B = GLOB.blob_nodes[i]
nodes["Blob Node #[i] ([B.overmind ? "[B.overmind.blob_reagent_datum.name]":"No Chemical"])"] = B
var/node_name = input(src, "Choose a node to jump to.", "Node Jump") in nodes
var/obj/structure/blob/node/chosen_node = nodes[node_name]
if(chosen_node)
forceMove(chosen_node.loc)
/mob/camera/blob/proc/createSpecial(price, blobType, nearEquals, needsNode, turf/T)
if(!T)
T = get_turf(src)
var/obj/structure/blob/B = (locate(/obj/structure/blob) in T)
if(!B)
to_chat(src, "<span class='warning'>There is no blob here!</span>")
return
if(!istype(B, /obj/structure/blob/normal))
to_chat(src, "<span class='warning'>Unable to use this blob, find a normal one.</span>")
return
if(needsNode && nodes_required)
if(!(locate(/obj/structure/blob/node) in orange(3, T)) && !(locate(/obj/structure/blob/core) in orange(4, T)))
to_chat(src, "<span class='warning'>You need to place this blob closer to a node or core!</span>")
return //handholdotron 2000
if(nearEquals)
for(var/obj/structure/blob/L in orange(nearEquals, T))
if(L.type == blobType)
to_chat(src, "<span class='warning'>There is a similar blob nearby, move more than [nearEquals] tiles away from it!</span>")
return
if(!can_buy(price))
return
var/obj/structure/blob/N = B.change_to(blobType, src)
return N
/mob/camera/blob/verb/toggle_node_req()
set category = "Blob"
set name = "Toggle Node Requirement"
set desc = "Toggle requiring nodes to place resource and factory blobs."
nodes_required = !nodes_required
if(nodes_required)
to_chat(src, "<span class='warning'>You now require a nearby node or core to place factory and resource blobs.</span>")
else
to_chat(src, "<span class='warning'>You no longer require a nearby node or core to place factory and resource blobs.</span>")
/mob/camera/blob/verb/create_shield_power()
set category = "Blob"
set name = "Create Shield Blob (15)"
set desc = "Create a shield blob, which will block fire and is hard to kill."
create_shield()
/mob/camera/blob/proc/create_shield(turf/T)
createSpecial(15, /obj/structure/blob/shield, 0, 0, T)
/mob/camera/blob/verb/create_resource()
set category = "Blob"
set name = "Create Resource Blob (40)"
set desc = "Create a resource tower which will generate resources for you."
createSpecial(40, /obj/structure/blob/resource, 4, 1)
/mob/camera/blob/verb/create_node()
set category = "Blob"
set name = "Create Node Blob (50)"
set desc = "Create a node, which will power nearby factory and resource blobs."
createSpecial(50, /obj/structure/blob/node, 5, 0)
/mob/camera/blob/verb/create_factory()
set category = "Blob"
set name = "Create Factory Blob (60)"
set desc = "Create a spore tower that will spawn spores to harass your enemies."
createSpecial(60, /obj/structure/blob/factory, 7, 1)
/mob/camera/blob/verb/create_blobbernaut()
set category = "Blob"
set name = "Create Blobbernaut (40)"
set desc = "Create a powerful blobbernaut which is mildly smart and will attack enemies."
var/turf/T = get_turf(src)
var/obj/structure/blob/factory/B = locate(/obj/structure/blob/factory) in T
if(!B)
to_chat(src, "<span class='warning'>You must be on a factory blob!</span>")
return
if(B.naut) //if it already made a blobbernaut, it can't do it again
to_chat(src, "<span class='warning'>This factory blob is already sustaining a blobbernaut.</span>")
return
if(B.obj_integrity < B.max_integrity * 0.5)
to_chat(src, "<span class='warning'>This factory blob is too damaged to sustain a blobbernaut.</span>")
return
if(!can_buy(40))
return
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as a [blob_reagent_datum.name] blobbernaut?", ROLE_BLOB, null, ROLE_BLOB, 50) //players must answer rapidly
if(LAZYLEN(candidates)) //if we got at least one candidate, they're a blobbernaut now.
B.max_integrity = initial(B.max_integrity) * 0.25 //factories that produced a blobbernaut have much lower health
B.obj_integrity = min(B.obj_integrity, B.max_integrity)
B.update_icon()
B.visible_message("<span class='warning'><b>The blobbernaut [pick("rips", "tears", "shreds")] its way out of the factory blob!</b></span>")
playsound(B.loc, 'sound/effects/splat.ogg', 50, 1)
var/mob/living/simple_animal/hostile/blob/blobbernaut/blobber = new /mob/living/simple_animal/hostile/blob/blobbernaut(get_turf(B))
flick("blobbernaut_produce", blobber)
B.naut = blobber
blobber.factory = B
blobber.overmind = src
blobber.update_icons()
blobber.adjustHealth(blobber.maxHealth * 0.5)
blob_mobs += blobber
var/mob/dead/observer/C = pick(candidates)
blobber.key = C.key
SEND_SOUND(blobber, sound('sound/effects/blobattack.ogg'))
SEND_SOUND(blobber, sound('sound/effects/attackblob.ogg'))
to_chat(blobber, "<b>You are a blobbernaut!</b>")
to_chat(blobber, "You are powerful, hard to kill, and slowly regenerate near nodes and cores, <span class='cultlarge'>but will slowly die if not near the blob</span> or if the factory that made you is killed.")
to_chat(blobber, "You can communicate with other blobbernauts and overminds via <b>:b</b>")
to_chat(blobber, "Your overmind's blob reagent is: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(blobber, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]")
else
to_chat(src, "<span class='warning'>You could not conjure a sentience for your blobbernaut. Your points have been refunded. Try again later.</span>")
add_points(40)
/mob/camera/blob/verb/relocate_core()
set category = "Blob"
set name = "Relocate Core (80)"
set desc = "Swaps the locations of your core and the selected node."
var/turf/T = get_turf(src)
var/obj/structure/blob/node/B = locate(/obj/structure/blob/node) in T
if(!B)
to_chat(src, "<span class='warning'>You must be on a blob node!</span>")
return
if(!blob_core)
to_chat(src, "<span class='userdanger'>You have no core and are about to die! May you rest in peace.</span>")
return
var/area/A = get_area(T)
if(isspaceturf(T) || A && !A.blob_allowed)
to_chat(src, "<span class='warning'>You cannot relocate your core here!</span>")
return
if(!can_buy(80))
return
var/turf/old_turf = get_turf(blob_core)
var/olddir = blob_core.dir
blob_core.forceMove(T)
blob_core.setDir(B.dir)
B.forceMove(old_turf)
B.setDir(olddir)
/mob/camera/blob/verb/revert()
set category = "Blob"
set name = "Remove Blob"
set desc = "Removes a blob, giving you back some resources."
var/turf/T = get_turf(src)
remove_blob(T)
/mob/camera/blob/proc/remove_blob(turf/T)
var/obj/structure/blob/B = locate() in T
if(!B)
to_chat(src, "<span class='warning'>There is no blob there!</span>")
return
if(B.point_return < 0)
to_chat(src, "<span class='warning'>Unable to remove this blob.</span>")
return
if(max_blob_points < B.point_return + blob_points)
to_chat(src, "<span class='warning'>You have too many resources to remove this blob!</span>")
return
if(B.point_return)
add_points(B.point_return)
to_chat(src, "<span class='notice'>Gained [B.point_return] resources from removing \the [B].</span>")
qdel(B)
/mob/camera/blob/verb/expand_blob_power()
set category = "Blob"
set name = "Expand/Attack Blob (4)"
set desc = "Attempts to create a new blob in this tile. If the tile isn't clear, instead attacks it, damaging mobs and objects."
var/turf/T = get_turf(src)
expand_blob(T)
/mob/camera/blob/proc/expand_blob(turf/T)
if(world.time < last_attack)
return
var/list/possibleblobs = list()
for(var/obj/structure/blob/AB in range(T, 1))
possibleblobs += AB
if(!possibleblobs.len)
to_chat(src, "<span class='warning'>There is no blob adjacent to the target tile!</span>")
return
if(can_buy(4))
var/attacksuccess = FALSE
for(var/mob/living/L in T)
if(ROLE_BLOB in L.faction) //no friendly/dead fire
continue
if(L.stat != DEAD)
attacksuccess = TRUE
var/mob_protection = L.get_permeability_protection()
blob_reagent_datum.reaction_mob(L, VAPOR, 25, 1, mob_protection, src)
blob_reagent_datum.send_message(L)
var/obj/structure/blob/B = locate() in T
if(B)
if(attacksuccess) //if we successfully attacked a turf with a blob on it, don't refund shit
B.blob_attack_animation(T, src)
else
to_chat(src, "<span class='warning'>There is a blob there!</span>")
add_points(4) //otherwise, refund all of the cost
else
var/list/cardinalblobs = list()
var/list/diagonalblobs = list()
for(var/I in possibleblobs)
var/obj/structure/blob/IB = I
if(get_dir(IB, T) in GLOB.cardinals)
cardinalblobs += IB
else
diagonalblobs += IB
var/obj/structure/blob/OB
if(cardinalblobs.len)
OB = pick(cardinalblobs)
OB.expand(T, src)
else
OB = pick(diagonalblobs)
if(attacksuccess)
OB.blob_attack_animation(T, src)
playsound(OB, 'sound/effects/splat.ogg', 50, 1)
else
add_points(4) //if we're attacking diagonally and didn't hit anything, refund
if(attacksuccess)
last_attack = world.time + CLICK_CD_MELEE
else
last_attack = world.time + CLICK_CD_RAPID
/mob/camera/blob/verb/rally_spores_power()
set category = "Blob"
set name = "Rally Spores"
set desc = "Rally your spores to move to a target location."
var/turf/T = get_turf(src)
rally_spores(T)
/mob/camera/blob/proc/rally_spores(turf/T)
to_chat(src, "You rally your spores.")
var/list/surrounding_turfs = block(locate(T.x - 1, T.y - 1, T.z), locate(T.x + 1, T.y + 1, T.z))
if(!surrounding_turfs.len)
return
for(var/mob/living/simple_animal/hostile/blob/blobspore/BS in blob_mobs)
if(isturf(BS.loc) && get_dist(BS, T) <= 35)
BS.LoseTarget()
BS.Goto(pick(surrounding_turfs), BS.move_to_delay)
/mob/camera/blob/verb/blob_broadcast()
set category = "Blob"
set name = "Blob Broadcast"
set desc = "Speak with your blob spores and blobbernauts as your mouthpieces."
var/speak_text = input(src, "What would you like to say with your minions?", "Blob Broadcast", null) as text
if(!speak_text)
return
else
to_chat(src, "You broadcast with your minions, <B>[speak_text]</B>")
for(var/BLO in blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
if(BM.stat == CONSCIOUS)
BM.say(speak_text)
/mob/camera/blob/verb/chemical_reroll()
set category = "Blob"
set name = "Reactive Chemical Adaptation (40)"
set desc = "Replaces your chemical with a random, different one."
if(free_chem_rerolls || can_buy(40))
set_chemical()
if(free_chem_rerolls)
free_chem_rerolls--
/mob/camera/blob/proc/set_chemical()
var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob) - blob_reagent_datum.type))
blob_reagent_datum = new BC
color = blob_reagent_datum.complementary_color
for(var/BL in GLOB.blobs)
var/obj/structure/blob/B = BL
B.update_icon()
for(var/BLO in blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
BM.update_icons() //If it's getting a new chemical, tell it what it does!
to_chat(BM, "Your overmind's blob reagent is now: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(BM, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]")
to_chat(src, "Your reagent is now: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.description]")
if(blob_reagent_datum.effectdesc)
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.effectdesc]")
/mob/camera/blob/verb/blob_help()
set category = "Blob"
set name = "*Blob Help*"
set desc = "Help on how to blob."
to_chat(src, "<b>As the overmind, you can control the blob!</b>")
to_chat(src, "Your blob reagent is: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.description]")
if(blob_reagent_datum.effectdesc)
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.effectdesc]")
to_chat(src, "<b>You can expand, which will attack people, damage objects, or place a Normal Blob if the tile is clear.</b>")
to_chat(src, "<i>Normal Blobs</i> will expand your reach and can be upgraded into special blobs that perform certain functions.")
to_chat(src, "<b>You can upgrade normal blobs into the following types of blob:</b>")
to_chat(src, "<i>Shield Blobs</i> are strong and expensive blobs which take more damage. In additon, they are fireproof and can block air, use these to protect yourself from station fires.")
to_chat(src, "<i>Resource Blobs</i> are blobs which produce more resources for you, build as many of these as possible to consume the station. This type of blob must be placed near node blobs or your core to work.")
to_chat(src, "<i>Factory Blobs</i> are blobs that spawn blob spores which will attack nearby enemies. This type of blob must be placed near node blobs or your core to work.")
to_chat(src, "<i>Blobbernauts</i> can be produced from factories for a cost, and are hard to kill, powerful, and moderately smart. The factory used to create one will become fragile and briefly unable to produce spores.")
to_chat(src, "<i>Node Blobs</i> are blobs which grow, like the core. Like the core it can activate resource and factory blobs.")
to_chat(src, "<b>In addition to the buttons on your HUD, there are a few click shortcuts to speed up expansion and defense.</b>")
to_chat(src, "<b>Shortcuts:</b> Click = Expand Blob <b>|</b> Middle Mouse Click = Rally Spores <b>|</b> Ctrl Click = Create Shield Blob <b>|</b> Alt Click = Remove Blob")
to_chat(src, "Attempting to talk will send a message to all other overminds, allowing you to coordinate with them.")
if(!placed && autoplace_max_time <= world.time)
to_chat(src, "<span class='big'><font color=\"#EE4000\">You will automatically place your blob core in [DisplayTimeText(autoplace_max_time - world.time)].</font></span>")
to_chat(src, "<span class='big'><font color=\"#EE4000\">You [manualplace_min_time ? "will be able to":"can"] manually place your blob core by pressing the Place Blob Core button in the bottom right corner of the screen.</font></span>")
@@ -0,0 +1,361 @@
//I will need to recode parts of this but I am way too tired atm //I don't know who left this comment but they never did come back
/obj/structure/blob
name = "blob"
icon = 'icons/mob/blob.dmi'
light_range = 2
desc = "A thick wall of writhing tendrils."
density = FALSE //this being false causes two bugs, being able to attack blob tiles behind other blobs and being unable to move on blob tiles in no gravity, but turning it to 1 causes the blob mobs to be unable to path through blobs, which is probably worse.
opacity = 0
anchored = TRUE
layer = BELOW_MOB_LAYER
CanAtmosPass = ATMOS_PASS_PROC
var/point_return = 0 //How many points the blob gets back when it removes a blob of that type. If less than 0, blob cannot be removed.
max_integrity = 30
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70)
var/health_regen = 2 //how much health this blob regens when pulsed
var/pulse_timestamp = 0 //we got pulsed when?
var/heal_timestamp = 0 //we got healed when?
var/brute_resist = 0.5 //multiplies brute damage by this
var/fire_resist = 1 //multiplies burn damage by this
var/atmosblock = FALSE //if the blob blocks atmos and heat spread
var/mob/camera/blob/overmind
/obj/structure/blob/Initialize(mapload, owner_overmind)
. = ..()
if(owner_overmind)
overmind = owner_overmind
var/area/Ablob = get_area(src)
if(Ablob.blob_allowed) //Is this area allowed for winning as blob?
overmind.blobs_legit += src
GLOB.blobs += src //Keep track of the blob in the normal list either way
setDir(pick(GLOB.cardinals))
update_icon()
if(atmosblock)
air_update_turf(1)
ConsumeTile()
/obj/structure/blob/proc/creation_action() //When it's created by the overmind, do this.
return
/obj/structure/blob/Destroy()
if(atmosblock)
atmosblock = FALSE
air_update_turf(1)
if(overmind)
overmind.blobs_legit -= src //if it was in the legit blobs list, it isn't now
GLOB.blobs -= src //it's no longer in the all blobs list either
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) //Expand() is no longer broken, no check necessary.
return ..()
/obj/structure/blob/blob_act()
return
/obj/structure/blob/Adjacent(var/atom/neighbour)
. = ..()
if(.)
var/result = 0
var/direction = get_dir(src, neighbour)
var/list/dirs = list("[NORTHWEST]" = list(NORTH, WEST), "[NORTHEAST]" = list(NORTH, EAST), "[SOUTHEAST]" = list(SOUTH, EAST), "[SOUTHWEST]" = list(SOUTH, WEST))
for(var/A in dirs)
if(direction == text2num(A))
for(var/B in dirs[A])
var/C = locate(/obj/structure/blob) in get_step(src, B)
if(C)
result++
. -= result - 1
/obj/structure/blob/BlockSuperconductivity()
return atmosblock
/obj/structure/blob/CanPass(atom/movable/mover, turf/target)
if(istype(mover) && (mover.pass_flags & PASSBLOB))
return 1
return 0
/obj/structure/blob/CanAtmosPass(turf/T)
return !atmosblock
/obj/structure/blob/CanAStarPass(ID, dir, caller)
. = 0
if(ismovableatom(caller))
var/atom/movable/mover = caller
. = . || (mover.pass_flags & PASSBLOB)
/obj/structure/blob/update_icon() //Updates color based on overmind color if we have an overmind.
if(overmind)
add_atom_colour(overmind.blob_reagent_datum.color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
/obj/structure/blob/process()
Life()
/obj/structure/blob/proc/Life()
return
/obj/structure/blob/proc/Pulse_Area(mob/camera/blob/pulsing_overmind, claim_range = 10, pulse_range = 3, expand_range = 2)
if(QDELETED(pulsing_overmind))
pulsing_overmind = overmind
Be_Pulsed()
var/expanded = FALSE
if(prob(70) && expand())
expanded = TRUE
var/list/blobs_to_affect = list()
for(var/obj/structure/blob/B in urange(claim_range, src, 1))
blobs_to_affect += B
shuffle_inplace(blobs_to_affect)
for(var/L in blobs_to_affect)
var/obj/structure/blob/B = L
if(!B.overmind && !istype(B, /obj/structure/blob/core) && prob(30))
B.overmind = pulsing_overmind //reclaim unclaimed, non-core blobs.
B.update_icon()
var/distance = get_dist(get_turf(src), get_turf(B))
var/expand_probablity = max(20 - distance * 8, 1)
if(B.Adjacent(src))
expand_probablity = 20
if(distance <= expand_range)
var/can_expand = TRUE
if(blobs_to_affect.len >= 120 && B.heal_timestamp > world.time)
can_expand = FALSE
if(can_expand && B.pulse_timestamp <= world.time && prob(expand_probablity))
var/obj/structure/blob/newB = B.expand(null, null, !expanded) //expansion falls off with range but is faster near the blob causing the expansion
if(newB)
if(expanded)
qdel(newB)
expanded = TRUE
if(distance <= pulse_range)
B.Be_Pulsed()
/obj/structure/blob/proc/Be_Pulsed()
if(pulse_timestamp <= world.time)
ConsumeTile()
if(heal_timestamp <= world.time)
obj_integrity = min(max_integrity, obj_integrity+health_regen)
heal_timestamp = world.time + 20
update_icon()
pulse_timestamp = world.time + 10
return 1 //we did it, we were pulsed!
return 0 //oh no we failed
/obj/structure/blob/proc/ConsumeTile()
for(var/atom/A in loc)
A.blob_act(src)
if(iswallturf(loc))
loc.blob_act(src) //don't ask how a wall got on top of the core, just eat it
/obj/structure/blob/proc/blob_attack_animation(atom/A = null, controller) //visually attacks an atom
var/obj/effect/temp_visual/blob/O = new /obj/effect/temp_visual/blob(src.loc)
O.setDir(dir)
if(controller)
var/mob/camera/blob/BO = controller
O.color = BO.blob_reagent_datum.color
O.alpha = 200
else if(overmind)
O.color = overmind.blob_reagent_datum.color
if(A)
O.do_attack_animation(A) //visually attack the whatever
return O //just in case you want to do something to the animation.
/obj/structure/blob/proc/expand(turf/T = null, controller = null, expand_reaction = 1)
if(!T)
var/list/dirs = list(1,2,4,8)
for(var/i = 1 to 4)
var/dirn = pick(dirs)
dirs.Remove(dirn)
T = get_step(src, dirn)
if(!(locate(/obj/structure/blob) in T))
break
else
T = null
if(!T)
return 0
var/make_blob = TRUE //can we make a blob?
if(isspaceturf(T) && !(locate(/obj/structure/lattice) in T) && prob(80))
make_blob = FALSE
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) //Let's give some feedback that we DID try to spawn in space, since players are used to it
ConsumeTile() //hit the tile we're in, making sure there are no border objects blocking us
if(!T.CanPass(src, T)) //is the target turf impassable
make_blob = FALSE
T.blob_act(src) //hit the turf if it is
for(var/atom/A in T)
if(!A.CanPass(src, T)) //is anything in the turf impassable
make_blob = FALSE
A.blob_act(src) //also hit everything in the turf
if(make_blob) //well, can we?
var/obj/structure/blob/B = new /obj/structure/blob/normal(src.loc, (controller || overmind))
B.density = TRUE
if(T.Enter(B,src)) //NOW we can attempt to move into the tile
B.density = initial(B.density)
B.forceMove(T)
B.update_icon()
if(B.overmind && expand_reaction)
B.overmind.blob_reagent_datum.expand_reaction(src, B, T, controller)
return B
else
blob_attack_animation(T, controller)
T.blob_act(src) //if we can't move in hit the turf again
qdel(B) //we should never get to this point, since we checked before moving in. destroy the blob so we don't have two blobs on one tile
return null
else
blob_attack_animation(T, controller) //if we can't, animate that we attacked
return null
/obj/structure/blob/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(severity > 0)
if(overmind)
overmind.blob_reagent_datum.emp_reaction(src, severity)
if(prob(100 - severity * 30))
new /obj/effect/temp_visual/emp(get_turf(src))
/obj/structure/blob/tesla_act(power)
..()
if(overmind)
if(overmind.blob_reagent_datum.tesla_reaction(src, power))
take_damage(power/400, BURN, "energy")
else
take_damage(power/400, BURN, "energy")
/obj/structure/blob/extinguish()
..()
if(overmind)
overmind.blob_reagent_datum.extinguish_reaction(src)
/obj/structure/blob/hulk_damage()
return 15
/obj/structure/blob/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/analyzer))
user.changeNext_move(CLICK_CD_MELEE)
to_chat(user, "<b>The analyzer beeps once, then reports:</b><br>")
SEND_SOUND(user, sound('sound/machines/ping.ogg'))
if(overmind)
to_chat(user, "<b>Progress to Critical Mass:</b> <span class='notice'>[overmind.blobs_legit.len]/[overmind.blobwincount].</span>")
chemeffectreport(user)
else
to_chat(user, "<b>Blob core neutralized. Critical mass no longer attainable.</b>")
typereport(user)
else
return ..()
/obj/structure/blob/proc/chemeffectreport(mob/user)
if(overmind)
to_chat(user, "<b>Material: <font color=\"[overmind.blob_reagent_datum.color]\">[overmind.blob_reagent_datum.name]</font><span class='notice'>.</span></b>")
to_chat(user, "<b>Material Effects:</b> <span class='notice'>[overmind.blob_reagent_datum.analyzerdescdamage]</span>")
to_chat(user, "<b>Material Properties:</b> <span class='notice'>[overmind.blob_reagent_datum.analyzerdesceffect]</span><br>")
else
to_chat(user, "<b>No Material Detected!</b><br>")
/obj/structure/blob/proc/typereport(mob/user)
to_chat(user, "<b>Blob Type:</b> <span class='notice'>[uppertext(initial(name))]</span>")
to_chat(user, "<b>Health:</b> <span class='notice'>[obj_integrity]/[max_integrity]</span>")
to_chat(user, "<b>Effects:</b> <span class='notice'>[scannerreport()]</span>")
/obj/structure/blob/attack_animal(mob/living/simple_animal/M)
if(ROLE_BLOB in M.faction) //sorry, but you can't kill the blob as a blobbernaut
return
..()
/obj/structure/blob/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
switch(damage_type)
if(BRUTE)
if(damage_amount)
playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1)
else
playsound(src, 'sound/weapons/tap.ogg', 50, 1)
if(BURN)
playsound(src.loc, 'sound/items/welder.ogg', 100, 1)
/obj/structure/blob/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir)
switch(damage_type)
if(BRUTE)
damage_amount *= brute_resist
if(BURN)
damage_amount *= fire_resist
if(CLONE)
else
return 0
var/armor_protection = 0
if(damage_flag)
armor_protection = armor.getRating(damage_flag)
damage_amount = round(damage_amount * (100 - armor_protection)*0.01, 0.1)
if(overmind && damage_flag)
damage_amount = overmind.blob_reagent_datum.damage_reaction(src, damage_amount, damage_type, damage_flag)
return damage_amount
/obj/structure/blob/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
. = ..()
if(. && obj_integrity > 0)
update_icon()
/obj/structure/blob/obj_destruction(damage_flag)
if(overmind)
overmind.blob_reagent_datum.death_reaction(src, damage_flag)
..()
/obj/structure/blob/proc/change_to(type, controller)
if(!ispath(type))
throw EXCEPTION("change_to(): invalid type for blob")
return
var/obj/structure/blob/B = new type(src.loc, controller)
B.creation_action()
B.update_icon()
B.setDir(dir)
qdel(src)
return B
/obj/structure/blob/examine(mob/user)
..()
var/datum/atom_hud/hud_to_check = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
if(user.research_scanner || hud_to_check.hudusers[user])
to_chat(user, "<b>Your HUD displays an extensive report...</b><br>")
if(overmind)
to_chat(user, "<b>Progress to Critical Mass:</b> <span class='notice'>[overmind.blobs_legit.len]/[overmind.blobwincount].</span>")
else
to_chat(user, "<b>Core neutralized. Critical mass no longer attainable.</b>")
chemeffectreport(user)
typereport(user)
else
if(isobserver(user) && overmind)
to_chat(user, "<b>Progress to Critical Mass:</b> <span class='notice'>[overmind.blobs_legit.len]/[overmind.blobwincount].</span>")
to_chat(user, "It seems to be made of [get_chem_name()].")
/obj/structure/blob/proc/scannerreport()
return "A generic blob. Looks like someone forgot to override this proc, adminhelp this."
/obj/structure/blob/proc/get_chem_name()
if(overmind)
return overmind.blob_reagent_datum.name
return "an unknown variant"
/obj/structure/blob/normal
name = "normal blob"
icon_state = "blob"
light_range = 0
obj_integrity = 21 //doesn't start at full health
max_integrity = 25
health_regen = 1
brute_resist = 0.25
/obj/structure/blob/normal/scannerreport()
if(obj_integrity <= 15)
return "Currently weak to brute damage."
return "N/A"
/obj/structure/blob/normal/update_icon()
..()
if(obj_integrity <= 15)
icon_state = "blob_damaged"
name = "fragile blob"
desc = "A thin lattice of slightly twitching tendrils."
brute_resist = 0.5
else
icon_state = "blob"
name = "blob"
desc = "A thick wall of writhing tendrils."
brute_resist = 0.25
@@ -0,0 +1,80 @@
/proc/brainwash(mob/living/L, directives)
if(!L.mind)
return
if(!islist(directives))
directives = list(directives)
var/datum/mind/M = L.mind
var/datum/antagonist/brainwashed/B = M.has_antag_datum(/datum/antagonist/brainwashed)
if(B)
for(var/O in directives)
var/datum/objective/brainwashing/objective = new(O)
B.objectives += objective
M.objectives += objective
B.greet()
else
B = new()
for(var/O in directives)
var/datum/objective/brainwashing/objective = new(O)
B.objectives += objective
M.add_antag_datum(B)
var/begin_message = "<span class='deadsay'><b>[L]</b> has been brainwashed with the following objectives: "
var/obj_message = english_list(directives)
var/end_message = "</b>.</span>"
var/rendered = begin_message + obj_message + end_message
deadchat_broadcast(rendered, follow_target = L, turf_target = get_turf(L), message_type=DEADCHAT_REGULAR)
/datum/antagonist/brainwashed
name = "Brainwashed Victim"
job_rank = ROLE_BRAINWASHED
roundend_category = "brainwashed victims"
show_in_antagpanel = TRUE
antagpanel_category = "Other"
show_name_in_check_antagonists = TRUE
/datum/antagonist/brainwashed/on_gain()
owner.objectives |= objectives
. = ..()
/datum/antagonist/brainwashed/greet()
to_chat(owner, "<span class='warning'>Your mind reels as it begins focusing on a single purpose...</span>")
to_chat(owner, "<big><span class='warning'><b>Follow the Directives, at any cost!</b></span></big>")
var/i = 1
for(var/X in objectives)
var/datum/objective/O = X
to_chat(owner, "<b>[i].</b> [O.explanation_text]")
i++
/datum/antagonist/brainwashed/farewell()
to_chat(owner, "<span class='warning'>Your mind suddenly clears...</span>")
to_chat(owner, "<big><span class='warning'><b>You feel the weight of the Directives disappear! You no longer have to obey them.</b></span></big>")
owner.announce_objectives()
/datum/antagonist/brainwashed/admin_add(datum/mind/new_owner,mob/admin)
var/mob/living/carbon/C = new_owner.current
if(!istype(C))
return
var/list/objectives = list()
do
var/objective = stripped_input(admin, "Add an objective, or leave empty to finish.", "Brainwashing", null, MAX_MESSAGE_LEN)
if(objective)
objectives += objective
while(alert(admin,"Add another objective?","More Brainwashing","Yes","No") == "Yes")
if(alert(admin,"Confirm Brainwashing?","Are you sure?","Yes","No") == "No")
return
if(!LAZYLEN(objectives))
return
if(QDELETED(C))
to_chat(admin, "Mob doesn't exist anymore")
return
brainwash(C, objectives)
var/obj_list = english_list(objectives)
message_admins("[key_name_admin(admin)] has brainwashed [key_name_admin(C)] with the following objectives: [obj_list].")
log_admin("[key_name(admin)] has brainwashed [key_name(C)] with the following objectives: [obj_list].")
/datum/objective/brainwashing
completed = TRUE
+155
View File
@@ -0,0 +1,155 @@
/datum/antagonist/brother
name = "Brother"
antagpanel_category = "Brother"
job_rank = ROLE_BROTHER
var/special_role = ROLE_BROTHER
var/datum/team/brother_team/team
antag_moodlet = /datum/mood_event/focused
can_hijack = HIJACK_HIJACKER
/datum/antagonist/brother/create_team(datum/team/brother_team/new_team)
if(!new_team)
return
if(!istype(new_team))
stack_trace("Wrong team type passed to [type] initialization.")
team = new_team
/datum/antagonist/brother/get_team()
return team
/datum/antagonist/brother/on_gain()
SSticker.mode.brothers += owner
objectives += team.objectives
owner.objectives += objectives
owner.special_role = special_role
finalize_brother()
return ..()
/datum/antagonist/brother/on_removal()
SSticker.mode.brothers -= owner
if(owner.current)
to_chat(owner.current,"<span class='userdanger'>You are no longer the [special_role]!</span>")
owner.special_role = null
return ..()
/datum/antagonist/brother/proc/give_meeting_area()
if(!owner.current || !team || !team.meeting_area)
return
to_chat(owner.current, "<B>Your designated meeting area:</B> [team.meeting_area]")
antag_memory += "<b>Meeting Area</b>: [team.meeting_area]<br>"
/datum/antagonist/brother/greet()
var/brother_text = ""
var/list/brothers = team.members - owner
for(var/i = 1 to brothers.len)
var/datum/mind/M = brothers[i]
brother_text += M.name
if(i == brothers.len - 1)
brother_text += " and "
else if(i != brothers.len)
brother_text += ", "
to_chat(owner.current, "<B><font size=3 color=red>You are the [owner.special_role] of [brother_text].</font></B>")
to_chat(owner.current, "The Syndicate only accepts those that have proven themselves. Prove yourself and prove your [team.member_name]s by completing your objectives together!")
owner.announce_objectives()
give_meeting_area()
/datum/antagonist/brother/proc/finalize_brother()
SSticker.mode.update_brother_icons_added(owner)
/datum/antagonist/brother/admin_add(datum/mind/new_owner,mob/admin)
//show list of possible brothers
var/list/candidates = list()
for(var/mob/living/L in GLOB.alive_mob_list)
if(!L.mind || L.mind == new_owner || !can_be_owned(L.mind))
continue
candidates[L.mind.name] = L.mind
var/choice = input(admin,"Choose the blood brother.", "Brother") as null|anything in candidates
if(!choice)
return
var/datum/mind/bro = candidates[choice]
var/datum/team/brother_team/T = new
T.add_member(new_owner)
T.add_member(bro)
T.pick_meeting_area()
T.forge_brother_objectives()
new_owner.add_antag_datum(/datum/antagonist/brother,T)
bro.add_antag_datum(/datum/antagonist/brother, T)
T.update_name()
message_admins("[key_name_admin(admin)] made [new_owner.current] and [bro.current] into blood brothers.")
log_admin("[key_name(admin)] made [new_owner.current] and [bro.current] into blood brothers.")
/datum/team/brother_team
name = "brotherhood"
member_name = "blood brother"
var/meeting_area
var/static/meeting_areas = list("The Bar", "Dorms", "Escape Dock", "Arrivals", "Holodeck", "Primary Tool Storage", "Recreation Area", "Chapel", "Library")
/datum/team/brother_team/is_solo()
return FALSE
/datum/team/brother_team/proc/pick_meeting_area()
meeting_area = pick(meeting_areas)
meeting_areas -= meeting_area
/datum/team/brother_team/proc/update_name()
var/list/last_names = list()
for(var/datum/mind/M in members)
var/list/split_name = splittext(M.name," ")
last_names += split_name[split_name.len]
name = last_names.Join(" & ")
/datum/team/brother_team/roundend_report()
var/list/parts = list()
parts += "<span class='header'>The blood brothers of [name] were:</span>"
for(var/datum/mind/M in members)
parts += printplayer(M)
var/win = TRUE
var/objective_count = 1
for(var/datum/objective/objective in objectives)
if(objective.check_completion())
parts += "<B>Objective #[objective_count]</B>: [objective.explanation_text] <span class='greentext'><B>Success!</span>"
else
parts += "<B>Objective #[objective_count]</B>: [objective.explanation_text] <span class='redtext'>Fail.</span>"
win = FALSE
objective_count++
if(win)
parts += "<span class='greentext'>The blood brothers were successful!</span>"
else
parts += "<span class='redtext'>The blood brothers have failed!</span>"
return "<div class='panel redborder'>[parts.Join("<br>")]</div>"
/datum/team/brother_team/proc/add_objective(datum/objective/O, needs_target = FALSE)
O.team = src
if(needs_target)
O.find_target()
O.update_explanation_text()
objectives += O
/datum/team/brother_team/proc/forge_brother_objectives()
objectives = list()
var/is_hijacker = prob(10)
for(var/i = 1 to max(1, CONFIG_GET(number/brother_objectives_amount) + (members.len > 2) - is_hijacker))
forge_single_objective()
if(is_hijacker)
if(!locate(/datum/objective/hijack) in objectives)
add_objective(new/datum/objective/hijack)
else if(!locate(/datum/objective/escape) in objectives)
add_objective(new/datum/objective/escape)
/datum/team/brother_team/proc/forge_single_objective()
if(prob(50))
if(LAZYLEN(active_ais()) && prob(100/GLOB.joined_player_list.len))
add_objective(new/datum/objective/destroy, TRUE)
else if(prob(30))
add_objective(new/datum/objective/maroon, TRUE)
else
add_objective(new/datum/objective/assassinate, TRUE)
else
add_objective(new/datum/objective/steal, TRUE)
/datum/team/brother_team/antag_listing_name()
return "[name] blood brothers"
@@ -0,0 +1,87 @@
// cellular emporium
// The place where changelings go to buy their biological weaponry.
/datum/cellular_emporium
var/name = "cellular emporium"
var/datum/antagonist/changeling/changeling
/datum/cellular_emporium/New(my_changeling)
. = ..()
changeling = my_changeling
/datum/cellular_emporium/Destroy()
changeling = null
. = ..()
/datum/cellular_emporium/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cellular_emporium", name, 900, 480, master_ui, state)
ui.open()
/datum/cellular_emporium/ui_data(mob/user)
var/list/data = list()
var/can_readapt = changeling.canrespec
var/genetic_points_remaining = changeling.geneticpoints
var/absorbed_dna_count = changeling.absorbedcount
var/true_absorbs = changeling.trueabsorbs
data["can_readapt"] = can_readapt
data["genetic_points_remaining"] = genetic_points_remaining
data["absorbed_dna_count"] = absorbed_dna_count
var/list/abilities = list()
for(var/path in changeling.all_powers)
var/obj/effect/proc_holder/changeling/ability = path
var/dna_cost = initial(ability.dna_cost)
if(dna_cost <= 0)
continue
var/list/AL = list()
AL["name"] = initial(ability.name)
AL["desc"] = initial(ability.desc)
AL["helptext"] = initial(ability.helptext)
AL["owned"] = changeling.has_sting(ability)
var/req_dna = initial(ability.req_dna)
var/req_absorbs = initial(ability.req_absorbs)
AL["dna_cost"] = dna_cost
AL["can_purchase"] = ((req_absorbs <= true_absorbs) && (req_dna <= absorbed_dna_count) && (dna_cost <= genetic_points_remaining))
abilities += list(AL)
data["abilities"] = abilities
return data
/datum/cellular_emporium/ui_act(action, params)
if(..())
return
switch(action)
if("readapt")
if(changeling.canrespec)
changeling.readapt()
if("evolve")
var/sting_name = params["name"]
changeling.purchase_power(sting_name)
/datum/action/innate/cellular_emporium
name = "Cellular Emporium"
icon_icon = 'icons/obj/drinks.dmi'
button_icon_state = "changelingsting"
background_icon_state = "bg_ling"
var/datum/cellular_emporium/cellular_emporium
/datum/action/innate/cellular_emporium/New(our_target)
. = ..()
button.name = name
if(istype(our_target, /datum/cellular_emporium))
cellular_emporium = our_target
else
throw EXCEPTION("cellular_emporium action created with non emporium")
/datum/action/innate/cellular_emporium/Activate()
cellular_emporium.ui_interact(owner)

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