mirror of
https://github.com/KabKebab/GS13.git
synced 2026-07-20 04:17:45 +01:00
Uploading all files.
This commit is contained in:
@@ -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())]"
|
||||
@@ -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
|
||||
@@ -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 ..()
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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'> </td>"
|
||||
output += "</tr>"
|
||||
qdel(query_search_bans)
|
||||
output += "</table></div>"
|
||||
|
||||
usr << browse(output,"window=lookupbans;size=900x500")
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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]")
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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!
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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."
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -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)]'>"
|
||||
@@ -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]")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>")
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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!
|
||||
@@ -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)]->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)]->[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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)]")
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
@@ -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 += " <b>ERROR</b><br>"
|
||||
continue
|
||||
for (var/filter in fqs.devices)
|
||||
var/list/f = fqs.devices[filter]
|
||||
if (!f)
|
||||
output += " [filter]: ERROR<br>"
|
||||
continue
|
||||
output += " [filter]: [f.len]<br>"
|
||||
for (var/device in f)
|
||||
if (istype(device, /atom))
|
||||
var/atom/A = device
|
||||
output += " [device] ([AREACOORD(A)])<br>"
|
||||
else
|
||||
output += " [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")
|
||||
@@ -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]")
|
||||
@@ -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
|
||||
@@ -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>")
|
||||
@@ -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(""))
|
||||
@@ -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]")
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.")
|
||||
@@ -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!
|
||||
@@ -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!
|
||||
@@ -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")
|
||||
@@ -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!
|
||||
@@ -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>")
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user