Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into Ghommie-cit655
This commit is contained in:
@@ -74,6 +74,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
|
||||
/client/proc/addbunkerbypass,
|
||||
/client/proc/revokebunkerbypass,
|
||||
/client/proc/stop_sounds,
|
||||
/client/proc/mark_datum_mapview,
|
||||
/client/proc/hide_verbs, /*hides all our adminverbs*/
|
||||
/client/proc/hide_most_verbs, /*hides all our hideable adminverbs*/
|
||||
/datum/admins/proc/open_borgopanel
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
|
||||
/client/proc/callproc()
|
||||
set category = "Debug"
|
||||
set name = "Advanced ProcCall"
|
||||
set waitfor = FALSE
|
||||
callproc_blocking()
|
||||
|
||||
/client/proc/callproc_blocking(list/get_retval)
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
|
||||
var/datum/target
|
||||
var/targetselected = FALSE
|
||||
var/returnval
|
||||
|
||||
switch(alert("Proc owned by something?",,"Yes","No"))
|
||||
if("Yes")
|
||||
targetselected = TRUE
|
||||
var/list/value = vv_get_value(default_class = VV_ATOM_REFERENCE, classes = list(VV_ATOM_REFERENCE, VV_DATUM_REFERENCE, VV_MOB_REFERENCE, VV_CLIENT, VV_MARKED_DATUM, VV_TEXT_LOCATE, VV_PROCCALL_RETVAL))
|
||||
if (!value["class"] || !value["value"])
|
||||
return
|
||||
target = value["value"]
|
||||
if(!istype(target))
|
||||
to_chat(usr, "<span class='danger'>Invalid target.</span>")
|
||||
return
|
||||
if("No")
|
||||
target = null
|
||||
targetselected = FALSE
|
||||
|
||||
var/procpath = input("Proc path, eg: /proc/fake_blood","Path:", null) as text|null
|
||||
if(!procpath)
|
||||
return
|
||||
|
||||
//strip away everything but the proc name
|
||||
var/list/proclist = splittext(procpath, "/")
|
||||
if (!length(proclist))
|
||||
return
|
||||
|
||||
var/procname = proclist[proclist.len]
|
||||
var/proctype = ("verb" in proclist) ? "verb" :"proc"
|
||||
|
||||
if(targetselected)
|
||||
if(!hascall(target, procname))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc(): type [target.type] has no [proctype] named [procpath].</span>")
|
||||
return
|
||||
else
|
||||
procpath = "/[proctype]/[procname]"
|
||||
if(!text2path(procpath))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc(): [procpath] does not exist.</span>")
|
||||
return
|
||||
|
||||
var/list/lst = get_callproc_args()
|
||||
if(!lst)
|
||||
return
|
||||
|
||||
if(targetselected)
|
||||
if(!target)
|
||||
to_chat(usr, "<font color='red'>Error: callproc(): owner of proc no longer exists.</font>")
|
||||
return
|
||||
var/msg = "[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]."
|
||||
log_admin(msg)
|
||||
message_admins(msg) //Proccall announce removed.
|
||||
admin_ticket_log(target, msg)
|
||||
returnval = WrapAdminProcCall(target, procname, lst) // Pass the lst as an argument list to the proc
|
||||
else
|
||||
//this currently has no hascall protection. wasn't able to get it working.
|
||||
log_admin("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].")
|
||||
message_admins("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") //Proccall announce removed.
|
||||
returnval = WrapAdminProcCall(GLOBAL_PROC, procname, lst) // Pass the lst as an argument list to the proc
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Advanced ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(get_retval)
|
||||
get_retval += returnval
|
||||
. = get_callproc_returnval(returnval, procname)
|
||||
if(.)
|
||||
to_chat(usr, .)
|
||||
|
||||
GLOBAL_VAR(AdminProcCaller)
|
||||
GLOBAL_PROTECT(AdminProcCaller)
|
||||
GLOBAL_VAR_INIT(AdminProcCallCount, 0)
|
||||
GLOBAL_PROTECT(AdminProcCallCount)
|
||||
GLOBAL_VAR(LastAdminCalledTargetRef)
|
||||
GLOBAL_PROTECT(LastAdminCalledTargetRef)
|
||||
GLOBAL_VAR(LastAdminCalledTarget)
|
||||
GLOBAL_PROTECT(LastAdminCalledTarget)
|
||||
GLOBAL_VAR(LastAdminCalledProc)
|
||||
GLOBAL_PROTECT(LastAdminCalledProc)
|
||||
GLOBAL_LIST_EMPTY(AdminProcCallSpamPrevention)
|
||||
GLOBAL_PROTECT(AdminProcCallSpamPrevention)
|
||||
|
||||
/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
|
||||
if(target != GLOBAL_PROC && procname == "Del")
|
||||
to_chat(usr, "<span class='warning'>Calling Del() is not allowed</span>")
|
||||
return
|
||||
|
||||
if(target != GLOBAL_PROC && !target.CanProcCall(procname))
|
||||
to_chat(usr, "Proccall on [target.type]/proc/[procname] is disallowed!")
|
||||
return
|
||||
var/current_caller = GLOB.AdminProcCaller
|
||||
var/ckey = usr ? usr.client.ckey : GLOB.AdminProcCaller
|
||||
if(!ckey)
|
||||
CRASH("WrapAdminProcCall with no ckey: [target] [procname] [english_list(arguments)]")
|
||||
if(current_caller && current_caller != ckey)
|
||||
if(!GLOB.AdminProcCallSpamPrevention[ckey])
|
||||
to_chat(usr, "<span class='adminnotice'>Another set of admin called procs are still running, your proc will be run after theirs finish.</span>")
|
||||
GLOB.AdminProcCallSpamPrevention[ckey] = TRUE
|
||||
UNTIL(!GLOB.AdminProcCaller)
|
||||
to_chat(usr, "<span class='adminnotice'>Running your proc</span>")
|
||||
GLOB.AdminProcCallSpamPrevention -= ckey
|
||||
else
|
||||
UNTIL(!GLOB.AdminProcCaller)
|
||||
GLOB.LastAdminCalledProc = procname
|
||||
if(target != GLOBAL_PROC)
|
||||
GLOB.LastAdminCalledTargetRef = "[REF(target)]"
|
||||
GLOB.AdminProcCaller = ckey //if this runtimes, too bad for you
|
||||
++GLOB.AdminProcCallCount
|
||||
. = world.WrapAdminProcCall(target, procname, arguments)
|
||||
if(--GLOB.AdminProcCallCount == 0)
|
||||
GLOB.AdminProcCaller = null
|
||||
|
||||
//adv proc call this, ya nerds
|
||||
/world/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
|
||||
if(target == GLOBAL_PROC)
|
||||
return call(procname)(arglist(arguments))
|
||||
else if(target != world)
|
||||
return call(target, procname)(arglist(arguments))
|
||||
else
|
||||
log_admin_private("[key_name(usr)] attempted to call world/proc/[procname] with arguments: [english_list(arguments)]")
|
||||
|
||||
/proc/IsAdminAdvancedProcCall()
|
||||
#ifdef TESTING
|
||||
return FALSE
|
||||
#else
|
||||
return usr && usr.client && GLOB.AdminProcCaller == usr.client.ckey
|
||||
#endif
|
||||
|
||||
/client/proc/callproc_datum(datum/A as null|area|mob|obj|turf)
|
||||
set category = "Debug"
|
||||
set name = "Atom ProcCall"
|
||||
set waitfor = FALSE
|
||||
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
|
||||
var/procname = input("Proc name, eg: fake_blood","Proc:", null) as text|null
|
||||
if(!procname)
|
||||
return
|
||||
if(!hascall(A,procname))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc_datum(): type [A.type] has no proc named [procname].</span>")
|
||||
return
|
||||
var/list/lst = get_callproc_args()
|
||||
if(!lst)
|
||||
return
|
||||
|
||||
if(!A || !IsValidSrc(A))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc_datum(): owner of proc no longer exists.</span>")
|
||||
return
|
||||
var/msg = "[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]."
|
||||
log_admin(msg)
|
||||
message_admins(msg)
|
||||
admin_ticket_log(A, msg)
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Atom ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
var/returnval = WrapAdminProcCall(A, procname, lst) // Pass the lst as an argument list to the proc
|
||||
. = get_callproc_returnval(returnval,procname)
|
||||
if(.)
|
||||
to_chat(usr, .)
|
||||
|
||||
/client/proc/get_callproc_args()
|
||||
var/argnum = input("Number of arguments","Number:",0) as num|null
|
||||
if(isnull(argnum))
|
||||
return
|
||||
|
||||
. = list()
|
||||
var/list/named_args = list()
|
||||
while(argnum--)
|
||||
var/named_arg = input("Leave blank for positional argument. Positional arguments will be considered as if they were added first.", "Named argument") as text|null
|
||||
var/value = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
|
||||
if (!value["class"])
|
||||
return
|
||||
if(named_arg)
|
||||
named_args[named_arg] = value["value"]
|
||||
else
|
||||
. += value["value"]
|
||||
if(LAZYLEN(named_args))
|
||||
. += named_args
|
||||
|
||||
/client/proc/get_callproc_returnval(returnval,procname)
|
||||
. = ""
|
||||
if(islist(returnval))
|
||||
var/list/returnedlist = returnval
|
||||
. = "<span class='notice'>"
|
||||
if(returnedlist.len)
|
||||
var/assoc_check = returnedlist[1]
|
||||
if(istext(assoc_check) && (returnedlist[assoc_check] != null))
|
||||
. += "[procname] returned an associative list:"
|
||||
for(var/key in returnedlist)
|
||||
. += "\n[key] = [returnedlist[key]]"
|
||||
|
||||
else
|
||||
. += "[procname] returned a list:"
|
||||
for(var/elem in returnedlist)
|
||||
. += "\n[elem]"
|
||||
else
|
||||
. = "[procname] returned an empty list"
|
||||
. += "</span>"
|
||||
|
||||
else
|
||||
. = "<span class='notice'>[procname] returned: [!isnull(returnval) ? returnval : "null"]</span>"
|
||||
@@ -1732,9 +1732,8 @@
|
||||
var/mob/M = locate(href_list["makeeligible"])
|
||||
if(!ismob(M))
|
||||
to_chat(usr, "this can only be used on instances of type /mob.")
|
||||
var/datum/element/ghost_role_eligibility/eli = SSdcs.GetElement(list(/datum/element/ghost_role_eligibility))
|
||||
if(M.ckey in eli.timeouts)
|
||||
eli.timeouts -= M.ckey
|
||||
if(M.ckey in GLOB.client_ghost_timeouts)
|
||||
GLOB.client_ghost_timeouts -= M.ckey
|
||||
|
||||
else if(href_list["sendtoprison"])
|
||||
if(!check_rights(R_ADMIN))
|
||||
|
||||
@@ -15,214 +15,6 @@
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Debug Two") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
|
||||
/* 21st Sept 2010
|
||||
Updated by Skie -- Still not perfect but better!
|
||||
Stuff you can't do:
|
||||
Call proc /mob/proc/Dizzy() for some player
|
||||
Because if you select a player mob as owner it tries to do the proc for
|
||||
/mob/living/carbon/human/ instead. And that gives a run-time error.
|
||||
But you can call procs that are of type /mob/living/carbon/human/proc/ for that player.
|
||||
*/
|
||||
|
||||
/client/proc/callproc()
|
||||
set category = "Debug"
|
||||
set name = "Advanced ProcCall"
|
||||
set waitfor = FALSE
|
||||
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
|
||||
var/datum/target = null
|
||||
var/targetselected = FALSE
|
||||
var/returnval = null
|
||||
|
||||
if(alert("Proc owned by something?",,"Yes","No") == "Yes")
|
||||
targetselected = TRUE
|
||||
var/list/value = vv_get_value(default_class = VV_ATOM_REFERENCE, classes = list(VV_ATOM_REFERENCE, VV_DATUM_REFERENCE, VV_MOB_REFERENCE, VV_CLIENT))
|
||||
if (!value["class"] || !value["value"])
|
||||
return
|
||||
target = value["value"]
|
||||
|
||||
var/procpath = input("Proc path, eg: /proc/fake_blood","Path:", null) as text|null
|
||||
if(!procpath)
|
||||
return
|
||||
|
||||
//strip away everything but the proc name
|
||||
var/list/proclist = splittext(procpath, "/")
|
||||
if (!length(proclist))
|
||||
return
|
||||
|
||||
var/procname = proclist[proclist.len]
|
||||
var/proctype = ("verb" in proclist) ? "verb" :"proc"
|
||||
|
||||
if(targetselected)
|
||||
if(!hascall(target, procname))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc(): type [target.type] has no [proctype] named [procpath].</span>")
|
||||
return
|
||||
else
|
||||
procpath = "/[proctype]/[procname]"
|
||||
if(!text2path(procpath))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc(): [procpath] does not exist.</span>")
|
||||
return
|
||||
|
||||
var/list/lst = get_callproc_args()
|
||||
if(!lst)
|
||||
return
|
||||
|
||||
if(targetselected)
|
||||
if(!target)
|
||||
to_chat(usr, "<span class='warning'>Error: callproc(): owner of proc no longer exists.</span>")
|
||||
return
|
||||
var/msg = "[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no argument"]."
|
||||
log_admin(msg)
|
||||
message_admins(msg)
|
||||
admin_ticket_log(target, msg)
|
||||
returnval = WrapAdminProcCall(target, procname, lst)
|
||||
else
|
||||
var/msg = "[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no argument"]."
|
||||
log_admin(msg)
|
||||
message_admins(msg)
|
||||
returnval = WrapAdminProcCall(GLOBAL_PROC, procpath, lst) //calling globals needs full qualified name (e.g /proc/foo)
|
||||
. = get_callproc_returnval(returnval, procname)
|
||||
if(.)
|
||||
to_chat(usr, .)
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Advanced ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
GLOBAL_VAR(AdminProcCaller)
|
||||
GLOBAL_PROTECT(AdminProcCaller)
|
||||
GLOBAL_VAR_INIT(AdminProcCallCount, 0)
|
||||
GLOBAL_PROTECT(AdminProcCallCount)
|
||||
GLOBAL_VAR(LastAdminCalledTargetRef)
|
||||
GLOBAL_PROTECT(LastAdminCalledTargetRef)
|
||||
GLOBAL_VAR(LastAdminCalledTarget)
|
||||
GLOBAL_PROTECT(LastAdminCalledTarget)
|
||||
GLOBAL_VAR(LastAdminCalledProc)
|
||||
GLOBAL_PROTECT(LastAdminCalledProc)
|
||||
GLOBAL_LIST_EMPTY(AdminProcCallSpamPrevention)
|
||||
GLOBAL_PROTECT(AdminProcCallSpamPrevention)
|
||||
|
||||
/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
|
||||
if(target != GLOBAL_PROC && procname == "Del")
|
||||
to_chat(usr, "<span class='warning'>Calling Del() is not allowed</span>")
|
||||
return
|
||||
|
||||
if(target != GLOBAL_PROC && !target.CanProcCall(procname))
|
||||
to_chat(usr, "Proccall on [target.type]/proc/[procname] is disallowed!")
|
||||
return
|
||||
var/current_caller = GLOB.AdminProcCaller
|
||||
var/ckey = usr ? usr.client.ckey : GLOB.AdminProcCaller
|
||||
if(!ckey)
|
||||
CRASH("WrapAdminProcCall with no ckey: [target] [procname] [english_list(arguments)]")
|
||||
if(current_caller && current_caller != ckey)
|
||||
if(!GLOB.AdminProcCallSpamPrevention[ckey])
|
||||
to_chat(usr, "<span class='adminnotice'>Another set of admin called procs are still running, your proc will be run after theirs finish.</span>")
|
||||
GLOB.AdminProcCallSpamPrevention[ckey] = TRUE
|
||||
UNTIL(!GLOB.AdminProcCaller)
|
||||
to_chat(usr, "<span class='adminnotice'>Running your proc</span>")
|
||||
GLOB.AdminProcCallSpamPrevention -= ckey
|
||||
else
|
||||
UNTIL(!GLOB.AdminProcCaller)
|
||||
GLOB.LastAdminCalledProc = procname
|
||||
if(target != GLOBAL_PROC)
|
||||
GLOB.LastAdminCalledTargetRef = "[REF(target)]"
|
||||
GLOB.AdminProcCaller = ckey //if this runtimes, too bad for you
|
||||
++GLOB.AdminProcCallCount
|
||||
. = world.WrapAdminProcCall(target, procname, arguments)
|
||||
if(--GLOB.AdminProcCallCount == 0)
|
||||
GLOB.AdminProcCaller = null
|
||||
|
||||
//adv proc call this, ya nerds
|
||||
/world/proc/WrapAdminProcCall(datum/target, procname, list/arguments)
|
||||
if(target == GLOBAL_PROC)
|
||||
return call(procname)(arglist(arguments))
|
||||
else if(target != world)
|
||||
return call(target, procname)(arglist(arguments))
|
||||
else
|
||||
log_admin_private("[key_name(usr)] attempted to call world/proc/[procname] with arguments: [english_list(arguments)]")
|
||||
|
||||
/proc/IsAdminAdvancedProcCall()
|
||||
#ifdef TESTING
|
||||
return FALSE
|
||||
#else
|
||||
return usr && usr.client && GLOB.AdminProcCaller == usr.client.ckey
|
||||
#endif
|
||||
|
||||
/client/proc/callproc_datum(datum/A as null|area|mob|obj|turf)
|
||||
set category = "Debug"
|
||||
set name = "Atom ProcCall"
|
||||
set waitfor = FALSE
|
||||
|
||||
if(!check_rights(R_DEBUG))
|
||||
return
|
||||
|
||||
var/procname = input("Proc name, eg: fake_blood","Proc:", null) as text|null
|
||||
if(!procname)
|
||||
return
|
||||
if(!hascall(A,procname))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc_datum(): type [A.type] has no proc named [procname].</span>")
|
||||
return
|
||||
var/list/lst = get_callproc_args()
|
||||
if(!lst)
|
||||
return
|
||||
|
||||
if(!A || !IsValidSrc(A))
|
||||
to_chat(usr, "<span class='warning'>Error: callproc_datum(): owner of proc no longer exists.</span>")
|
||||
return
|
||||
var/msg = "[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]."
|
||||
log_admin(msg)
|
||||
message_admins(msg)
|
||||
admin_ticket_log(A, msg)
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Atom ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
var/returnval = WrapAdminProcCall(A, procname, lst) // Pass the lst as an argument list to the proc
|
||||
. = get_callproc_returnval(returnval,procname)
|
||||
if(.)
|
||||
to_chat(usr, .)
|
||||
|
||||
/client/proc/get_callproc_args()
|
||||
var/argnum = input("Number of arguments","Number:",0) as num|null
|
||||
if(isnull(argnum))
|
||||
return
|
||||
|
||||
. = list()
|
||||
var/list/named_args = list()
|
||||
while(argnum--)
|
||||
var/named_arg = input("Leave blank for positional argument. Positional arguments will be considered as if they were added first.", "Named argument") as text|null
|
||||
var/value = vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
|
||||
if (!value["class"])
|
||||
return
|
||||
if(named_arg)
|
||||
named_args[named_arg] = value["value"]
|
||||
else
|
||||
. += value["value"]
|
||||
if(LAZYLEN(named_args))
|
||||
. += named_args
|
||||
|
||||
/client/proc/get_callproc_returnval(returnval,procname)
|
||||
. = ""
|
||||
if(islist(returnval))
|
||||
var/list/returnedlist = returnval
|
||||
. = "<span class='notice'>"
|
||||
if(returnedlist.len)
|
||||
var/assoc_check = returnedlist[1]
|
||||
if(istext(assoc_check) && (returnedlist[assoc_check] != null))
|
||||
. += "[procname] returned an associative list:"
|
||||
for(var/key in returnedlist)
|
||||
. += "\n[key] = [returnedlist[key]]"
|
||||
|
||||
else
|
||||
. += "[procname] returned a list:"
|
||||
for(var/elem in returnedlist)
|
||||
. += "\n[elem]"
|
||||
else
|
||||
. = "[procname] returned an empty list"
|
||||
. += "</span>"
|
||||
|
||||
else
|
||||
. = "<span class='notice'>[procname] returned: [!isnull(returnval) ? returnval : "null"]</span>"
|
||||
|
||||
/client/proc/Cell()
|
||||
set category = "Debug"
|
||||
set name = "Air Status in Location"
|
||||
@@ -343,66 +135,6 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
|
||||
else
|
||||
alert("Invalid mob")
|
||||
|
||||
/proc/make_types_fancy(var/list/types)
|
||||
if (ispath(types))
|
||||
types = list(types)
|
||||
. = list()
|
||||
for(var/type in types)
|
||||
var/typename = "[type]"
|
||||
var/static/list/TYPES_SHORTCUTS = list(
|
||||
/obj/effect/decal/cleanable = "CLEANABLE",
|
||||
/obj/item/radio/headset = "HEADSET",
|
||||
/obj/item/clothing/head/helmet/space = "SPESSHELMET",
|
||||
/obj/item/book/manual = "MANUAL",
|
||||
/obj/item/reagent_containers/food/drinks = "DRINK", //longest paths comes first
|
||||
/obj/item/reagent_containers/food = "FOOD",
|
||||
/obj/item/reagent_containers = "REAGENT_CONTAINERS",
|
||||
/obj/machinery/atmospherics = "ATMOS_MECH",
|
||||
/obj/machinery/portable_atmospherics = "PORT_ATMOS",
|
||||
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack = "MECHA_MISSILE_RACK",
|
||||
/obj/item/mecha_parts/mecha_equipment = "MECHA_EQUIP",
|
||||
/obj/item/organ = "ORGAN",
|
||||
/obj/item = "ITEM",
|
||||
/obj/machinery = "MACHINERY",
|
||||
/obj/effect = "EFFECT",
|
||||
/obj = "O",
|
||||
/datum = "D",
|
||||
/turf/open = "OPEN",
|
||||
/turf/closed = "CLOSED",
|
||||
/turf = "T",
|
||||
/mob/living/carbon = "CARBON",
|
||||
/mob/living/simple_animal = "SIMPLE",
|
||||
/mob/living = "LIVING",
|
||||
/mob = "M"
|
||||
)
|
||||
for (var/tn in TYPES_SHORTCUTS)
|
||||
if (copytext(typename,1, length("[tn]/")+1)=="[tn]/" /*findtextEx(typename,"[tn]/",1,2)*/ )
|
||||
typename = TYPES_SHORTCUTS[tn]+copytext(typename,length("[tn]/"))
|
||||
break
|
||||
.[typename] = type
|
||||
|
||||
/proc/get_fancy_list_of_atom_types()
|
||||
var/static/list/pre_generated_list
|
||||
if (!pre_generated_list) //init
|
||||
pre_generated_list = make_types_fancy(typesof(/atom))
|
||||
return pre_generated_list
|
||||
|
||||
|
||||
/proc/get_fancy_list_of_datum_types()
|
||||
var/static/list/pre_generated_list
|
||||
if (!pre_generated_list) //init
|
||||
pre_generated_list = make_types_fancy(sortList(typesof(/datum) - typesof(/atom)))
|
||||
return pre_generated_list
|
||||
|
||||
|
||||
/proc/filter_fancy_list(list/L, filter as text)
|
||||
var/list/matches = new
|
||||
for(var/key in L)
|
||||
var/value = L[key]
|
||||
if(findtext("[key]", filter) || findtext("[value]", filter))
|
||||
matches[key] = value
|
||||
return matches
|
||||
|
||||
//TODO: merge the vievars version into this or something maybe mayhaps
|
||||
/client/proc/cmd_debug_del_all(object as text)
|
||||
set category = "Debug"
|
||||
|
||||
@@ -594,31 +594,6 @@ Traitors and the like can also be revived with the previous role mostly intact.
|
||||
|
||||
admin_delete(A)
|
||||
|
||||
/client/proc/admin_delete(datum/D)
|
||||
var/atom/A = D
|
||||
var/coords = ""
|
||||
var/jmp_coords = ""
|
||||
if(istype(A))
|
||||
var/turf/T = get_turf(A)
|
||||
if(T)
|
||||
coords = "at [COORD(T)]"
|
||||
jmp_coords = "at [ADMIN_COORDJMP(T)]"
|
||||
else
|
||||
jmp_coords = coords = "in nullspace"
|
||||
|
||||
if (alert(src, "Are you sure you want to delete:\n[D]\n[coords]?", "Confirmation", "Yes", "No") == "Yes")
|
||||
log_admin("[key_name(usr)] deleted [D] [coords]")
|
||||
message_admins("[key_name_admin(usr)] deleted [D] [jmp_coords]")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delete") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(isturf(D))
|
||||
var/turf/T = D
|
||||
T.ScrapeAway()
|
||||
else
|
||||
vv_update_display(D, "deleted", VV_MSG_DELETED)
|
||||
qdel(D)
|
||||
if(!QDELETED(D))
|
||||
vv_update_display(D, "deleted", "")
|
||||
|
||||
/client/proc/cmd_admin_list_open_jobs()
|
||||
set category = "Admin"
|
||||
set name = "Manage Job Slots"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/client/proc/admin_delete(datum/D)
|
||||
var/atom/A = D
|
||||
var/coords = ""
|
||||
var/jmp_coords = ""
|
||||
if(istype(A))
|
||||
var/turf/T = get_turf(A)
|
||||
if(T)
|
||||
coords = "at [COORD(T)]"
|
||||
jmp_coords = "at [ADMIN_COORDJMP(T)]"
|
||||
else
|
||||
jmp_coords = coords = "in nullspace"
|
||||
|
||||
if (alert(src, "Are you sure you want to delete:\n[D]\n[coords]?", "Confirmation", "Yes", "No") == "Yes")
|
||||
log_admin("[key_name(usr)] deleted [D] [coords]")
|
||||
message_admins("[key_name_admin(usr)] deleted [D] [jmp_coords]")
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delete") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
if(isturf(D))
|
||||
var/turf/T = D
|
||||
T.ScrapeAway()
|
||||
else
|
||||
vv_update_display(D, "deleted", VV_MSG_DELETED)
|
||||
qdel(D)
|
||||
if(!QDELETED(D))
|
||||
vv_update_display(D, "deleted", "")
|
||||
@@ -0,0 +1,76 @@
|
||||
#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing )
|
||||
/proc/debug_variable(name, value, level, datum/D, sanitize = TRUE) //if D is a list, name will be index, and value will be assoc value.
|
||||
var/header
|
||||
if(D)
|
||||
if(islist(D))
|
||||
var/index = name
|
||||
if (value)
|
||||
name = D[name] //name is really the index until this line
|
||||
else
|
||||
value = D[name]
|
||||
header = "<li style='backgroundColor:white'>([VV_HREF_TARGET_1V(D, VV_HK_LIST_EDIT, "E", index)]) ([VV_HREF_TARGET_1V(D, VV_HK_LIST_CHANGE, "C", index)]) ([VV_HREF_TARGET_1V(D, VV_HK_LIST_REMOVE, "-", index)]) "
|
||||
else
|
||||
header = "<li style='backgroundColor:white'>([VV_HREF_TARGET_1V(D, VV_HK_BASIC_EDIT, "E", name)]) ([VV_HREF_TARGET_1V(D, VV_HK_BASIC_CHANGE, "C", name)]) ([VV_HREF_TARGET_1V(D, VV_HK_BASIC_MASSEDIT, "M", name)]) "
|
||||
else
|
||||
header = "<li>"
|
||||
|
||||
var/item
|
||||
if (isnull(value))
|
||||
item = "[VV_HTML_ENCODE(name)] = <span class='value'>null</span>"
|
||||
|
||||
else if (istext(value))
|
||||
item = "[VV_HTML_ENCODE(name)] = <span class='value'>\"[VV_HTML_ENCODE(value)]\"</span>"
|
||||
|
||||
else if (isicon(value))
|
||||
#ifdef VARSICON
|
||||
var/icon/I = icon(value)
|
||||
var/rnd = rand(1,10000)
|
||||
var/rname = "tmp[REF(I)][rnd].png"
|
||||
usr << browse_rsc(I, rname)
|
||||
item = "[VV_HTML_ENCODE(name)] = (<span class='value'>[value]</span>) <img class=icon src=\"[rname]\">"
|
||||
#else
|
||||
item = "[VV_HTML_ENCODE(name)] = /icon (<span class='value'>[value]</span>)"
|
||||
#endif
|
||||
|
||||
else if (isfile(value))
|
||||
item = "[VV_HTML_ENCODE(name)] = <span class='value'>'[value]'</span>"
|
||||
|
||||
else if (istype(value, /datum))
|
||||
var/datum/DV = value
|
||||
if ("[DV]" != "[DV.type]") //if the thing as a name var, lets use it.
|
||||
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] [REF(value)]</a> = [DV] [DV.type]"
|
||||
else
|
||||
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] [REF(value)]</a> = [DV.type]"
|
||||
|
||||
else if (islist(value))
|
||||
var/list/L = value
|
||||
var/list/items = list()
|
||||
|
||||
if (L.len > 0 && !(name == "underlays" || name == "overlays" || L.len > (IS_NORMAL_LIST(L) ? VV_NORMAL_LIST_NO_EXPAND_THRESHOLD : VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD)))
|
||||
for (var/i in 1 to L.len)
|
||||
var/key = L[i]
|
||||
var/val
|
||||
if (IS_NORMAL_LIST(L) && !isnum(key))
|
||||
val = L[key]
|
||||
if (isnull(val)) // we still want to display non-null false values, such as 0 or ""
|
||||
val = key
|
||||
key = i
|
||||
|
||||
items += debug_variable(key, val, level + 1, sanitize = sanitize)
|
||||
|
||||
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] = /list ([L.len])</a><ul>[items.Join()]</ul>"
|
||||
else
|
||||
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] = /list ([L.len])</a>"
|
||||
|
||||
else if (name in GLOB.bitfields)
|
||||
var/list/flags = list()
|
||||
for (var/i in GLOB.bitfields[name])
|
||||
if (value & GLOB.bitfields[name][i])
|
||||
flags += i
|
||||
item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
|
||||
else
|
||||
item = "[VV_HTML_ENCODE(name)] = <span class='value'>[VV_HTML_ENCODE(value)]</span>"
|
||||
|
||||
return "[header][item]</li>"
|
||||
|
||||
#undef VV_HTML_ENCODE
|
||||
@@ -0,0 +1,271 @@
|
||||
/client/proc/vv_get_class(var_name, 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,
|
||||
VV_TEXT_LOCATE,
|
||||
VV_PROCCALL_RETVAL,
|
||||
)
|
||||
|
||||
var/markstring
|
||||
if(!(VV_MARKED_DATUM in restricted_classes))
|
||||
markstring = "[VV_MARKED_DATUM] (CURRENT: [(istype(holder) && istype(holder.marked_datum))? holder.marked_datum.type : "NULL"])"
|
||||
classes += markstring
|
||||
|
||||
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"] == markstring)
|
||||
.["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_PROCCALL_RETVAL)
|
||||
var/list/get_retval = list()
|
||||
callproc_blocking(get_retval)
|
||||
.["value"] = get_retval[1] //should have been set in proccall!
|
||||
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
|
||||
|
||||
if(VV_TEXT_LOCATE)
|
||||
var/datum/D
|
||||
do
|
||||
var/ref = input("Enter reference:", "Reference") as null|text
|
||||
if(!ref)
|
||||
break
|
||||
D = locate(ref)
|
||||
if(!D)
|
||||
alert("Invalid ref!")
|
||||
continue
|
||||
if(!D.can_vv_mark())
|
||||
alert("Datum can not be marked!")
|
||||
continue
|
||||
while(!D)
|
||||
.["type"] = D.type
|
||||
.["value"] = D
|
||||
@@ -0,0 +1,12 @@
|
||||
/client/proc/mark_datum(datum/D)
|
||||
if(!holder)
|
||||
return
|
||||
if(holder.marked_datum)
|
||||
vv_update_display(holder.marked_datum, "marked", "")
|
||||
holder.marked_datum = D
|
||||
vv_update_display(D, "marked", VV_MSG_MARKED)
|
||||
|
||||
/client/proc/mark_datum_mapview(datum/D as mob|obj|turf|area in view(view))
|
||||
set category = "Debug"
|
||||
set name = "Mark Object"
|
||||
mark_datum(D)
|
||||
+11
-5
@@ -197,7 +197,7 @@
|
||||
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)")
|
||||
|
||||
|
||||
//not using global lists as vv is a debug function and debug functions should rely on as less things as possible.
|
||||
/proc/get_all_of_type(var/T, subtypes = TRUE)
|
||||
var/list/typecache = list()
|
||||
typecache[T] = 1
|
||||
@@ -205,19 +205,25 @@
|
||||
typecache = typecacheof(typecache)
|
||||
. = list()
|
||||
if (ispath(T, /mob))
|
||||
for(var/mob/thing in GLOB.mob_list)
|
||||
for(var/mob/thing in world)
|
||||
if (typecache[thing.type])
|
||||
. += thing
|
||||
CHECK_TICK
|
||||
|
||||
else if (ispath(T, /obj/machinery/door))
|
||||
for(var/obj/machinery/door/thing in GLOB.airlocks)
|
||||
for(var/obj/machinery/door/thing in world)
|
||||
if (typecache[thing.type])
|
||||
. += thing
|
||||
CHECK_TICK
|
||||
|
||||
else if (ispath(T, /obj/machinery))
|
||||
for(var/obj/machinery/thing in GLOB.machines)
|
||||
for(var/obj/machinery/thing in world)
|
||||
if (typecache[thing.type])
|
||||
. += thing
|
||||
CHECK_TICK
|
||||
|
||||
else if (ispath(T, /obj/item))
|
||||
for(var/obj/item/thing in world)
|
||||
if (typecache[thing.type])
|
||||
. += thing
|
||||
CHECK_TICK
|
||||
@@ -247,7 +253,7 @@
|
||||
CHECK_TICK
|
||||
|
||||
else if (ispath(T, /client))
|
||||
for(var/client/thing in GLOB.clients)
|
||||
for(var/client/thing in world)
|
||||
if (typecache[thing.type])
|
||||
. += thing
|
||||
CHECK_TICK
|
||||
+25
-281
@@ -1,264 +1,12 @@
|
||||
GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "force_ending"))
|
||||
GLOBAL_LIST_INIT(VVlocked, list("vars", "datum_flags", "client", "mob")) //Requires DEBUG
|
||||
GLOBAL_PROTECT(VVlocked)
|
||||
GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays", "resize"))
|
||||
GLOBAL_LIST_INIT(VVicon_edit_lock, list("icon", "icon_state", "overlays", "underlays")) //Requires DEBUG or FUN
|
||||
GLOBAL_PROTECT(VVicon_edit_lock)
|
||||
GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey"))
|
||||
GLOBAL_LIST_INIT(VVckey_edit, list("key", "ckey")) //Requires DEBUG or SPAWN
|
||||
GLOBAL_PROTECT(VVckey_edit)
|
||||
GLOBAL_LIST_INIT(VVpixelmovement, list("step_x", "step_y", "bound_height", "bound_width", "bound_x", "bound_y"))
|
||||
GLOBAL_LIST_INIT(VVpixelmovement, list("bound_x", "bound_y", "step_x", "step_y", "step_size", "bound_height", "bound_width", "bounds")) //No editing ever.
|
||||
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")
|
||||
@@ -372,13 +120,13 @@ GLOBAL_PROTECT(VVpixelmovement)
|
||||
if(confirm != "Continue")
|
||||
return
|
||||
|
||||
|
||||
var/is_normal_list = IS_NORMAL_LIST(L)
|
||||
|
||||
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))
|
||||
if (is_normal_list && !isnum(key))
|
||||
value = L[key]
|
||||
if (value == null)
|
||||
value = "null"
|
||||
@@ -439,10 +187,17 @@ GLOBAL_PROTECT(VVpixelmovement)
|
||||
assoc_key = L[index]
|
||||
var/default
|
||||
var/variable
|
||||
if (assoc)
|
||||
variable = L[assoc_key]
|
||||
else
|
||||
variable = L[index]
|
||||
var/old_assoc_value //EXPERIMENTAL - Keep old associated value while modifying key, if any
|
||||
if(is_normal_list)
|
||||
if (assoc)
|
||||
variable = L[assoc_key]
|
||||
else
|
||||
variable = L[index]
|
||||
//EXPERIMENTAL - Keep old associated value while modifying key, if any
|
||||
var/found = L[variable]
|
||||
if(!isnull(found))
|
||||
old_assoc_value = found
|
||||
//
|
||||
|
||||
default = vv_get_class(objectvar, variable)
|
||||
|
||||
@@ -504,11 +259,13 @@ GLOBAL_PROTECT(VVpixelmovement)
|
||||
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(is_normal_list)
|
||||
if(assoc)
|
||||
L[assoc_key] = new_var
|
||||
else
|
||||
L[index] = new_var
|
||||
if(!isnull(old_assoc_value) && IS_VALID_ASSOC_KEY(new_var))
|
||||
L[new_var] = old_assoc_value
|
||||
if (O)
|
||||
if (O.vv_edit_var(objectvar, L) == FALSE)
|
||||
to_chat(src, "Your edit was rejected by the object.")
|
||||
@@ -527,15 +284,8 @@ GLOBAL_PROTECT(VVpixelmovement)
|
||||
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
|
||||
@@ -567,11 +317,6 @@ GLOBAL_PROTECT(VVpixelmovement)
|
||||
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)
|
||||
|
||||
@@ -635,10 +380,9 @@ GLOBAL_PROTECT(VVpixelmovement)
|
||||
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
|
||||
return TRUE
|
||||
@@ -0,0 +1,128 @@
|
||||
//DO NOT ADD MORE TO THIS FILE.
|
||||
//Use vv_do_topic() for datums!
|
||||
/client/proc/view_var_Topic(href, href_list, hsrc)
|
||||
if( (usr.client != src) || !src.holder || !holder.CheckAdminHref(href, href_list))
|
||||
return
|
||||
var/target = GET_VV_TARGET
|
||||
vv_do_basic(target, href_list, href)
|
||||
if(istype(target, /datum))
|
||||
var/datum/D = target
|
||||
D.vv_do_topic(href_list)
|
||||
else if(islist(target))
|
||||
vv_do_list(target, href_list)
|
||||
if(href_list["Vars"])
|
||||
debug_variables(locate(href_list["Vars"]))
|
||||
|
||||
//Stuff below aren't in dropdowns/etc.
|
||||
|
||||
if(check_rights(R_VAREDIT))
|
||||
|
||||
//~CARN: for renaming mobs (updates their name, real_name, mind.name, their ID/PDA and datacore records).
|
||||
|
||||
if(href_list["rename"])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
|
||||
var/mob/M = locate(href_list["rename"]) in GLOB.mob_list
|
||||
if(!istype(M))
|
||||
to_chat(usr, "This can only be used on instances of type /mob")
|
||||
return
|
||||
|
||||
var/new_name = stripped_input(usr,"What would you like to name this mob?","Input a name",M.real_name,MAX_NAME_LEN)
|
||||
if( !new_name || !M )
|
||||
return
|
||||
|
||||
message_admins("Admin [key_name_admin(usr)] renamed [key_name_admin(M)] to [new_name].")
|
||||
M.fully_replace_character_name(M.real_name,new_name)
|
||||
vv_update_display(M, "name", new_name)
|
||||
vv_update_display(M, "real_name", M.real_name || "No real name")
|
||||
|
||||
else if(href_list["rotatedatum"])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
|
||||
var/atom/A = locate(href_list["rotatedatum"])
|
||||
if(!istype(A))
|
||||
to_chat(usr, "This can only be done to instances of type /atom")
|
||||
return
|
||||
|
||||
switch(href_list["rotatedir"])
|
||||
if("right")
|
||||
A.setDir(turn(A.dir, -45))
|
||||
if("left")
|
||||
A.setDir(turn(A.dir, 45))
|
||||
vv_update_display(A, "dir", dir2text(A.dir))
|
||||
|
||||
|
||||
else if(href_list["makehuman"])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
|
||||
var/mob/living/carbon/monkey/Mo = locate(href_list["makehuman"]) in GLOB.mob_list
|
||||
if(!istype(Mo))
|
||||
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/monkey")
|
||||
return
|
||||
|
||||
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
|
||||
return
|
||||
if(!Mo)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
return
|
||||
holder.Topic(href, list("humanone"=href_list["makehuman"]))
|
||||
|
||||
else if(href_list["adjustDamage"] && href_list["mobToDamage"])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
|
||||
var/mob/living/L = locate(href_list["mobToDamage"]) in GLOB.mob_list
|
||||
if(!istype(L))
|
||||
return
|
||||
|
||||
var/Text = href_list["adjustDamage"]
|
||||
|
||||
var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num
|
||||
|
||||
if(!L)
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
return
|
||||
|
||||
var/newamt
|
||||
switch(Text)
|
||||
if("brute")
|
||||
L.adjustBruteLoss(amount)
|
||||
newamt = L.getBruteLoss()
|
||||
if("fire")
|
||||
L.adjustFireLoss(amount)
|
||||
newamt = L.getFireLoss()
|
||||
if("toxin")
|
||||
L.adjustToxLoss(amount)
|
||||
newamt = L.getToxLoss()
|
||||
if("oxygen")
|
||||
L.adjustOxyLoss(amount)
|
||||
newamt = L.getOxyLoss()
|
||||
if("brain")
|
||||
L.adjustOrganLoss(ORGAN_SLOT_BRAIN, amount)
|
||||
newamt = L.getOrganLoss(ORGAN_SLOT_BRAIN)
|
||||
if("clone")
|
||||
L.adjustCloneLoss(amount)
|
||||
newamt = L.getCloneLoss()
|
||||
if("stamina")
|
||||
L.adjustStaminaLoss(amount)
|
||||
newamt = L.getStaminaLoss()
|
||||
else
|
||||
to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]")
|
||||
return
|
||||
|
||||
if(amount != 0)
|
||||
var/log_msg = "[key_name(usr)] dealt [amount] amount of [Text] damage to [key_name(L)]"
|
||||
message_admins("[key_name(usr)] dealt [amount] amount of [Text] damage to [ADMIN_LOOKUPFLW(L)]")
|
||||
log_admin(log_msg)
|
||||
admin_ticket_log(L, "<font color='blue'>[log_msg]</font>")
|
||||
vv_update_display(L, Text, "[newamt]")
|
||||
|
||||
|
||||
//Finally, refresh if something modified the list.
|
||||
if(href_list["datumrefresh"])
|
||||
var/datum/DAT = locate(href_list["datumrefresh"])
|
||||
if(istype(DAT, /datum) || istype(DAT, /client))
|
||||
debug_variables(DAT)
|
||||
@@ -0,0 +1,51 @@
|
||||
//Not using datum.vv_do_topic for very basic/low level debug things, incase the datum's vv_do_topic is runtiming/whatnot.
|
||||
/client/proc/vv_do_basic(datum/target, href_list)
|
||||
var/target_var = GET_VV_VAR_TARGET
|
||||
if(check_rights(R_VAREDIT))
|
||||
if(target_var)
|
||||
if(href_list[VV_HK_BASIC_EDIT])
|
||||
if(!modify_variables(target, target_var, 1))
|
||||
return
|
||||
switch(target_var)
|
||||
if("name")
|
||||
vv_update_display(target, "name", "[target]")
|
||||
if("dir")
|
||||
var/atom/A = target
|
||||
if(istype(A))
|
||||
vv_update_display(target, "dir", dir2text(A.dir) || A.dir)
|
||||
if("ckey")
|
||||
var/mob/living/L = target
|
||||
if(istype(L))
|
||||
vv_update_display(target, "ckey", L.ckey || "No ckey")
|
||||
if("real_name")
|
||||
var/mob/living/L = target
|
||||
if(istype(L))
|
||||
vv_update_display(target, "real_name", L.real_name || "No real name")
|
||||
if(href_list[VV_HK_BASIC_CHANGE])
|
||||
modify_variables(target, target_var, 0)
|
||||
if(href_list[VV_HK_BASIC_MASSEDIT])
|
||||
cmd_mass_modify_object_variables(target, target_var)
|
||||
if(check_rights(R_ADMIN, FALSE))
|
||||
if(href_list[VV_HK_EXPOSE])
|
||||
var/value = vv_get_value(VV_CLIENT)
|
||||
if (value["class"] != VV_CLIENT)
|
||||
return
|
||||
var/client/C = value["value"]
|
||||
if (!C)
|
||||
return
|
||||
if(!target)
|
||||
to_chat(usr, "<span class='warning'>The object you tried to expose to [C] no longer exists (nulled or hard-deled)</span>")
|
||||
return
|
||||
message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a <a href='?_src_=vars;datumrefresh=[REF(target)]'>VV window</a>")
|
||||
log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [target]")
|
||||
to_chat(C, "[holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window")
|
||||
C.debug_variables(target)
|
||||
if(check_rights(R_DEBUG))
|
||||
if(href_list[VV_HK_DELETE])
|
||||
usr.client.admin_delete(target)
|
||||
if (isturf(src)) // show the turf that took its place
|
||||
usr.client.debug_variables(src)
|
||||
if(href_list[VV_HK_MARK])
|
||||
usr.client.mark_datum(target)
|
||||
if(href_list[VV_HK_CALLPROC])
|
||||
usr.client.callproc_datum(target)
|
||||
@@ -0,0 +1,43 @@
|
||||
//LISTS - CAN NOT DO VV_DO_TOPIC BECAUSE LISTS AREN'T DATUMS :(
|
||||
/client/proc/vv_do_list(list/target, href_list)
|
||||
var/target_index = text2num(GET_VV_VAR_TARGET)
|
||||
if(check_rights(R_VAREDIT))
|
||||
if(target_index)
|
||||
if(href_list[VV_HK_LIST_EDIT])
|
||||
mod_list(target, null, "list", "contents", target_index, autodetect_class = TRUE)
|
||||
if(href_list[VV_HK_LIST_CHANGE])
|
||||
mod_list(target, null, "list", "contents", target_index, autodetect_class = FALSE)
|
||||
if(href_list[VV_HK_LIST_REMOVE])
|
||||
var/variable = target[target_index]
|
||||
var/prompt = alert("Do you want to remove item number [target_index] from list?", "Confirm", "Yes", "No")
|
||||
if (prompt != "Yes")
|
||||
return
|
||||
target.Cut(target_index, target_index+1)
|
||||
log_world("### ListVarEdit by [src]: /list's contents: REMOVED=[html_encode("[variable]")]")
|
||||
log_admin("[key_name(src)] modified list's contents: REMOVED=[variable]")
|
||||
message_admins("[key_name_admin(src)] modified list's contents: REMOVED=[variable]")
|
||||
if(href_list[VV_HK_LIST_ADD])
|
||||
mod_list_add(target, null, "list", "contents")
|
||||
if(href_list[VV_HK_LIST_ERASE_DUPES])
|
||||
uniqueList_inplace(target)
|
||||
log_world("### ListVarEdit by [src]: /list contents: CLEAR DUPES")
|
||||
log_admin("[key_name(src)] modified list's contents: CLEAR DUPES")
|
||||
message_admins("[key_name_admin(src)] modified list's contents: CLEAR DUPES")
|
||||
if(href_list[VV_HK_LIST_ERASE_NULLS])
|
||||
listclearnulls(target)
|
||||
log_world("### ListVarEdit by [src]: /list contents: CLEAR NULLS")
|
||||
log_admin("[key_name(src)] modified list's contents: CLEAR NULLS")
|
||||
message_admins("[key_name_admin(src)] modified list's contents: CLEAR NULLS")
|
||||
if(href_list[VV_HK_LIST_SET_LENGTH])
|
||||
var/value = vv_get_value(VV_NUM)
|
||||
if (value["class"] != VV_NUM || value["value"] > max(50000, target.len)) //safety - would rather someone not put an extra 0 and erase the server's memory lmao.
|
||||
return
|
||||
target.len = value["value"]
|
||||
log_world("### ListVarEdit by [src]: /list len: [target.len]")
|
||||
log_admin("[key_name(src)] modified list's len: [target.len]")
|
||||
message_admins("[key_name_admin(src)] modified list's len: [target.len]")
|
||||
if(href_list[VV_HK_LIST_SHUFFLE])
|
||||
shuffle_inplace(target)
|
||||
log_world("### ListVarEdit by [src]: /list contents: SHUFFLE")
|
||||
log_admin("[key_name(src)] modified list's contents: SHUFFLE")
|
||||
message_admins("[key_name_admin(src)] modified list's contents: SHUFFLE")
|
||||
@@ -0,0 +1,269 @@
|
||||
|
||||
/client/proc/debug_variables(datum/D in world)
|
||||
set category = "Debug"
|
||||
set name = "View Variables"
|
||||
//set src in world
|
||||
var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round.
|
||||
|
||||
if(!usr.client || !usr.client.holder) //This is usr because admins can call the proc on other clients, even if they're not admins, to show them VVs.
|
||||
to_chat(usr, "<span class='danger'>You need to be an administrator to access this.</span>")
|
||||
return
|
||||
|
||||
if(!D)
|
||||
return
|
||||
|
||||
var/islist = islist(D)
|
||||
if(!islist && !istype(D))
|
||||
return
|
||||
|
||||
var/title = ""
|
||||
var/refid = REF(D)
|
||||
var/icon/sprite
|
||||
var/hash
|
||||
|
||||
var/type = islist? /list : D.type
|
||||
var/no_icon = FALSE
|
||||
|
||||
if(istype(D, /atom))
|
||||
sprite = getFlatIcon(D)
|
||||
hash = md5(sprite)
|
||||
if(sprite)
|
||||
hash = md5(sprite)
|
||||
src << browse_rsc(sprite, "vv[hash].png")
|
||||
else
|
||||
no_icon = TRUE
|
||||
|
||||
title = "[D] ([REF(D)]) = [type]"
|
||||
var/formatted_type = replacetext("[type]", "/", "<wbr>/")
|
||||
|
||||
var/sprite_text
|
||||
if(sprite)
|
||||
sprite_text = no_icon? "\[NO ICON\]" : "<img src='vv[hash].png'></td><td>"
|
||||
var/list/header = islist(D)? list("<b>/list</b>") : D.vv_get_header()
|
||||
|
||||
var/marked_line
|
||||
if(holder && holder.marked_datum && holder.marked_datum == D)
|
||||
marked_line = VV_MSG_MARKED
|
||||
var/varedited_line
|
||||
if(!islist && (D.datum_flags & DF_VAR_EDITED))
|
||||
varedited_line = VV_MSG_EDITED
|
||||
var/deleted_line
|
||||
if(!islist && D.gc_destroyed)
|
||||
deleted_line = VV_MSG_DELETED
|
||||
|
||||
var/list/dropdownoptions
|
||||
if (islist)
|
||||
dropdownoptions = list(
|
||||
"---",
|
||||
"Add Item" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ADD),
|
||||
"Remove Nulls" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ERASE_NULLS),
|
||||
"Remove Dupes" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ERASE_DUPES),
|
||||
"Set len" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SET_LENGTH),
|
||||
"Shuffle" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SHUFFLE),
|
||||
"Show VV To Player" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_EXPOSE),
|
||||
"---"
|
||||
)
|
||||
for(var/i in 1 to length(dropdownoptions))
|
||||
var/name = dropdownoptions[i]
|
||||
var/link = dropdownoptions[name]
|
||||
dropdownoptions[i] = "<option value[link? "='[link]'":""]>[name]</option>"
|
||||
else
|
||||
dropdownoptions = D.vv_get_dropdown()
|
||||
|
||||
var/list/names = list()
|
||||
if(!islist)
|
||||
for(var/V in D.vars)
|
||||
names += V
|
||||
sleep(1)
|
||||
|
||||
var/list/variable_html = list()
|
||||
if(islist)
|
||||
var/list/L = D
|
||||
for(var/i in 1 to L.len)
|
||||
var/key = L[i]
|
||||
var/value
|
||||
if(IS_NORMAL_LIST(L) && IS_VALID_ASSOC_KEY(key))
|
||||
value = L[key]
|
||||
variable_html += debug_variable(i, value, 0, L)
|
||||
else
|
||||
names = sortList(names)
|
||||
for(var/V in names)
|
||||
if(D.can_vv_get(V))
|
||||
variable_html += D.vv_get_var(V)
|
||||
|
||||
var/html = {"
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
|
||||
<title>[title]</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 9pt;
|
||||
}
|
||||
.value {
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 8pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload='selectTextField()' onkeydown='return handle_keydown()' onkeyup='handle_keyup()'>
|
||||
<script type="text/javascript">
|
||||
// onload
|
||||
function selectTextField() {
|
||||
var filter_text = document.getElementById('filter');
|
||||
filter_text.focus();
|
||||
filter_text.select();
|
||||
var lastsearch = getCookie("[refid][cookieoffset]search");
|
||||
if (lastsearch) {
|
||||
filter_text.value = lastsearch;
|
||||
updateSearch();
|
||||
}
|
||||
}
|
||||
function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0; i<ca.length; i++) {
|
||||
var c = ca\[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// main search functionality
|
||||
var last_filter = "";
|
||||
function updateSearch() {
|
||||
var filter = document.getElementById('filter').value.toLowerCase();
|
||||
var vars_ol = document.getElementById("vars");
|
||||
if (filter === last_filter) {
|
||||
// An event triggered an update but nothing has changed.
|
||||
return;
|
||||
} else if (filter.indexOf(last_filter) === 0) {
|
||||
// The new filter starts with the old filter, fast path by removing only.
|
||||
var children = vars_ol.childNodes;
|
||||
for (var i = children.length - 1; i >= 0; --i) {
|
||||
try {
|
||||
var li = children\[i];
|
||||
if (li.innerText.toLowerCase().indexOf(filter) == -1) {
|
||||
vars_ol.removeChild(li);
|
||||
}
|
||||
} catch(err) {}
|
||||
}
|
||||
} else {
|
||||
// Remove everything and put back what matches.
|
||||
while (vars_ol.hasChildNodes()) {
|
||||
vars_ol.removeChild(vars_ol.lastChild);
|
||||
}
|
||||
for (var i = 0; i < complete_list.length; ++i) {
|
||||
try {
|
||||
var li = complete_list\[i];
|
||||
if (!filter || li.innerText.toLowerCase().indexOf(filter) != -1) {
|
||||
vars_ol.appendChild(li);
|
||||
}
|
||||
} catch(err) {}
|
||||
}
|
||||
}
|
||||
last_filter = filter;
|
||||
document.cookie="[refid][cookieoffset]search="+encodeURIComponent(filter);
|
||||
}
|
||||
// onkeydown
|
||||
function handle_keydown() {
|
||||
if(event.keyCode == 116) { //F5 (to refresh properly)
|
||||
document.getElementById("refresh_link").click();
|
||||
event.preventDefault ? event.preventDefault() : (event.returnValue = false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// onkeyup
|
||||
function handle_keyup() {
|
||||
updateSearch();
|
||||
}
|
||||
// onchange
|
||||
function handle_dropdown(list) {
|
||||
var value = list.options\[list.selectedIndex].value;
|
||||
if (value !== "") {
|
||||
location.href = value;
|
||||
}
|
||||
list.selectedIndex = 0;
|
||||
document.getElementById('filter').focus();
|
||||
}
|
||||
// byjax
|
||||
function replace_span(what) {
|
||||
var idx = what.indexOf(':');
|
||||
document.getElementById(what.substr(0, idx)).innerHTML = what.substr(idx + 1);
|
||||
}
|
||||
</script>
|
||||
<div align='center'>
|
||||
<table width='100%'>
|
||||
<tr>
|
||||
<td width='50%'>
|
||||
<table align='center' width='100%'>
|
||||
<tr>
|
||||
<td>
|
||||
[sprite_text]
|
||||
<div align='center'>
|
||||
[header.Join()]
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div align='center'>
|
||||
<b><font size='1'>[formatted_type]</font></b>
|
||||
<span id='marked'>[marked_line]</span>
|
||||
<span id='varedited'>[varedited_line]</span>
|
||||
<span id='deleted'>[deleted_line]</span>
|
||||
</div>
|
||||
</td>
|
||||
<td width='50%'>
|
||||
<div align='center'>
|
||||
<a id='refresh_link' href='?_src_=vars;
|
||||
datumrefresh=[refid];[HrefToken()]'>Refresh</a>
|
||||
<form>
|
||||
<select name="file" size="1"
|
||||
onchange="handle_dropdown(this)"
|
||||
onmouseclick="this.focus()">
|
||||
<option value selected>Select option</option>
|
||||
[dropdownoptions.Join()]
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
<font size='1'>
|
||||
<b>E</b> - Edit, tries to determine the variable type by itself.<br>
|
||||
<b>C</b> - Change, asks you for the var type first.<br>
|
||||
<b>M</b> - Mass modify: changes this variable for all objects of this type.<br>
|
||||
</font>
|
||||
<hr>
|
||||
<table width='100%'>
|
||||
<tr>
|
||||
<td width='20%'>
|
||||
<div align='center'>
|
||||
<b>Search:</b>
|
||||
</div>
|
||||
</td>
|
||||
<td width='80%'>
|
||||
<input type='text' id='filter' name='filter_text' value='' style='width:100%;'>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<ol id='vars'>
|
||||
[variable_html.Join()]
|
||||
</ol>
|
||||
<script type='text/javascript'>
|
||||
var complete_list = \[\];
|
||||
var lis = document.getElementById("vars").children;
|
||||
for(var i = lis.length; i--;) complete_list\[i\] = lis\[i\];
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"}
|
||||
src << browse(html, "window=variables[refid];size=475x650")
|
||||
|
||||
/client/proc/vv_update_display(datum/D, span, content)
|
||||
src << output("[span]:[content]", "variables[REF(D)].browser:replace_span")
|
||||
@@ -31,4 +31,5 @@
|
||||
|
||||
/datum/antagonist/abductee/remove_innate_effects(mob/living/mob_override)
|
||||
update_abductor_icons_removed(mob_override ? mob_override.mind : owner)
|
||||
qdel(brain_trauma)
|
||||
if(!QDELETED(brain_trauma))
|
||||
qdel(brain_trauma)
|
||||
|
||||
@@ -92,10 +92,8 @@
|
||||
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()
|
||||
|
||||
/obj/item/clothing/suit/armor/abductor/vest/IsReflect()
|
||||
/obj/item/clothing/suit/armor/abductor/vest/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
. = ..()
|
||||
DeactivateStealth()
|
||||
|
||||
/obj/item/clothing/suit/armor/abductor/vest/ui_action_click()
|
||||
@@ -493,7 +491,7 @@
|
||||
|
||||
user.do_attack_animation(L)
|
||||
|
||||
if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
|
||||
if(L.run_block(src, 0, "[user]'s [src]", ATTACK_TYPE_MELEE, 0, user, check_zone(user.zone_selected)) & BLOCK_SUCCESS)
|
||||
playsound(L, 'sound/weapons/genhit.ogg', 50, TRUE)
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -441,23 +441,23 @@
|
||||
|
||||
var/remaining_uses //Set by the changeling ability.
|
||||
|
||||
/obj/item/shield/changeling/Initialize()
|
||||
/obj/item/shield/changeling/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
|
||||
if(ismob(loc))
|
||||
loc.visible_message("<span class='warning'>The end of [loc.name]\'s hand inflates rapidly, forming a huge shield-like mass!</span>", "<span class='warning'>We inflate our hand into a strong shield.</span>", "<span class='italics'>You hear organic matter ripping and tearing!</span>")
|
||||
|
||||
/obj/item/shield/changeling/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(remaining_uses < 1)
|
||||
/obj/item/shield/changeling/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses
|
||||
return ..()
|
||||
|
||||
/obj/item/shield/changeling/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
. = ..()
|
||||
if(--remaining_uses < 1)
|
||||
if(ishuman(loc))
|
||||
var/mob/living/carbon/human/H = loc
|
||||
H.visible_message("<span class='warning'>With a sickening crunch, [H] reforms [H.p_their()] shield into an arm!</span>", "<span class='notice'>We assimilate our shield into our body</span>", "<span class='italics>You hear organic matter ripping and tearing!</span>")
|
||||
qdel(src)
|
||||
return 0
|
||||
else
|
||||
remaining_uses--
|
||||
return ..()
|
||||
|
||||
|
||||
/***************************************\
|
||||
|*********SPACE SUIT + HELMET***********|
|
||||
|
||||
@@ -656,6 +656,7 @@
|
||||
C.equip_to_slot_or_del(new /obj/item/clothing/suit/cultrobes/alt(user), SLOT_WEAR_SUIT)
|
||||
C.equip_to_slot_or_del(new /obj/item/clothing/shoes/cult/alt(user), SLOT_SHOES)
|
||||
C.equip_to_slot_or_del(new /obj/item/storage/backpack/cultpack(user), SLOT_BACK)
|
||||
C.equip_to_slot_or_del(new /obj/item/clothing/gloves/fingerless/pugilist/hungryghost(user), SLOT_GLOVES)
|
||||
if(C == user)
|
||||
qdel(src) //Clears the hands
|
||||
C.put_in_hands(new /obj/item/melee/cultblade(user))
|
||||
|
||||
@@ -162,24 +162,20 @@
|
||||
jaunt.Remove(user)
|
||||
user.update_icons()
|
||||
|
||||
/obj/item/twohanded/required/cult_bastard/IsReflect()
|
||||
if(spinning)
|
||||
/obj/item/twohanded/required/cult_bastard/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(spinning && is_energy_reflectable_projectile(object) && (attack_type & ATTACK_TYPE_PROJECTILE))
|
||||
playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, 1)
|
||||
return TRUE
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/item/twohanded/required/cult_bastard/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT
|
||||
if(prob(final_block_chance))
|
||||
if(attack_type == PROJECTILE_ATTACK)
|
||||
if(attack_type & ATTACK_TYPE_PROJECTILE)
|
||||
owner.visible_message("<span class='danger'>[owner] deflects [attack_text] with [src]!</span>")
|
||||
playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, 1)
|
||||
return TRUE
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT
|
||||
else
|
||||
playsound(src, 'sound/weapons/parry.ogg', 75, 1)
|
||||
owner.visible_message("<span class='danger'>[owner] parries [attack_text] with [src]!</span>")
|
||||
return TRUE
|
||||
return FALSE
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/obj/item/twohanded/required/cult_bastard/afterattack(atom/target, mob/user, proximity, click_parameters)
|
||||
. = ..()
|
||||
@@ -353,6 +349,11 @@
|
||||
brightness_on = 0
|
||||
actions_types = list()
|
||||
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/cult/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/spellcasting, SPELL_CULT_HELMET, ITEM_SLOT_HEAD)
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/cult
|
||||
name = "\improper Nar'Sien hardened armor"
|
||||
icon_state = "cult_armor"
|
||||
@@ -363,6 +364,10 @@
|
||||
armor = list("melee" = 70, "bullet" = 50, "laser" = 30,"energy" = 15, "bomb" = 30, "bio" = 30, "rad" = 30, "fire" = 40, "acid" = 75)
|
||||
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/cult
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/cult/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/spellcasting, SPELL_CULT_ARMOR, ITEM_SLOT_OCLOTHING)
|
||||
|
||||
/obj/item/sharpener/cult
|
||||
name = "eldritch whetstone"
|
||||
desc = "A block, empowered by dark magic. Sharp weapons will be enhanced when used on the stone."
|
||||
@@ -414,7 +419,13 @@
|
||||
user.adjustBruteLoss(25)
|
||||
user.dropItemToGround(src, TRUE)
|
||||
|
||||
/obj/item/clothing/suit/hooded/cultrobes/cult_shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/clothing/suit/hooded/cultrobes/cult_shield/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(current_charges)
|
||||
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
|
||||
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + current_charges
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/suit/hooded/cultrobes/cult_shield/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(current_charges)
|
||||
owner.visible_message("<span class='danger'>\The [attack_text] is deflected in a burst of blood-red sparks!</span>")
|
||||
current_charges--
|
||||
@@ -422,8 +433,8 @@
|
||||
if(!current_charges)
|
||||
owner.visible_message("<span class='danger'>The runed shield around [owner] suddenly disappears!</span>")
|
||||
owner.update_inv_wear_suit()
|
||||
return 1
|
||||
return 0
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/obj/item/clothing/suit/hooded/cultrobes/cult_shield/worn_overlays(isinhands, icon_file, style_flags = NONE)
|
||||
. = list()
|
||||
@@ -725,19 +736,19 @@
|
||||
playsound(T, 'sound/effects/glassbr3.ogg', 100)
|
||||
qdel(src)
|
||||
|
||||
/obj/item/twohanded/cult_spear/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/twohanded/cult_spear/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(wielded)
|
||||
final_block_chance *= 2
|
||||
if(prob(final_block_chance))
|
||||
if(attack_type == PROJECTILE_ATTACK)
|
||||
if(attack_type & ATTACK_TYPE_PROJECTILE)
|
||||
owner.visible_message("<span class='danger'>[owner] deflects [attack_text] with [src]!</span>")
|
||||
playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, 1)
|
||||
return TRUE
|
||||
return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_REDIRECTED | BLOCK_PHYSICAL_EXTERNAL
|
||||
else
|
||||
playsound(src, 'sound/weapons/parry.ogg', 100, 1)
|
||||
owner.visible_message("<span class='danger'>[owner] parries [attack_text] with [src]!</span>")
|
||||
return TRUE
|
||||
return FALSE
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/datum/action/innate/cult/spear
|
||||
name = "Bloody Bond"
|
||||
@@ -936,10 +947,18 @@
|
||||
hitsound = 'sound/weapons/smash.ogg'
|
||||
var/illusions = 2
|
||||
|
||||
/obj/item/shield/mirror/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/shield/mirror/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
block_return[BLOCK_RETURN_REFLECT_PROJECTILE_CHANCE] = max(block_return[BLOCK_RETURN_REFLECT_PROJECTILE_CHANCE] || null, final_block_chance)
|
||||
return ..()
|
||||
|
||||
/obj/item/shield/mirror/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(iscultist(owner))
|
||||
if(istype(hitby, /obj/item/projectile))
|
||||
var/obj/item/projectile/P = hitby
|
||||
if(istype(object, /obj/item/projectile) && (attack_type == ATTACK_TYPE_PROJECTILE))
|
||||
if(is_energy_reflectable_projectile(object))
|
||||
if(prob(final_block_chance))
|
||||
return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED
|
||||
return BLOCK_NONE //To avoid reflection chance double-dipping with block chance
|
||||
var/obj/item/projectile/P = object
|
||||
if(P.damage >= 30)
|
||||
var/turf/T = get_turf(owner)
|
||||
T.visible_message("<span class='warning'>The sheer force from [P] shatters the mirror shield!</span>")
|
||||
@@ -947,11 +966,9 @@
|
||||
playsound(T, 'sound/effects/glassbr3.ogg', 100)
|
||||
owner.DefaultCombatKnockdown(25)
|
||||
qdel(src)
|
||||
return FALSE
|
||||
if(P.is_reflectable)
|
||||
return FALSE //To avoid reflection chance double-dipping with block chance
|
||||
return BLOCK_NONE
|
||||
. = ..()
|
||||
if(.)
|
||||
if(. & BLOCK_SUCCESS)
|
||||
playsound(src, 'sound/weapons/parry.ogg', 100, 1)
|
||||
if(illusions > 0)
|
||||
illusions--
|
||||
@@ -966,7 +983,7 @@
|
||||
E.Copy_Parent(owner, 70, 10)
|
||||
E.GiveTarget(owner)
|
||||
E.Goto(owner, owner.movement_delay(), E.minimum_distance)
|
||||
return TRUE
|
||||
return
|
||||
else
|
||||
if(prob(50))
|
||||
var/mob/living/simple_animal/hostile/illusion/H = new(owner.loc)
|
||||
@@ -975,7 +992,7 @@
|
||||
H.GiveTarget(owner)
|
||||
H.move_to_delay = owner.movement_delay()
|
||||
to_chat(owner, "<span class='danger'><b>[src] betrays you!</b></span>")
|
||||
return FALSE
|
||||
return BLOCK_NONE
|
||||
|
||||
/obj/item/shield/mirror/proc/readd()
|
||||
illusions++
|
||||
@@ -983,11 +1000,6 @@
|
||||
var/mob/living/holder = loc
|
||||
to_chat(holder, "<span class='cult italic'>The shield's illusions are back at full strength!</span>")
|
||||
|
||||
/obj/item/shield/mirror/IsReflect()
|
||||
if(prob(block_chance))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/item/shield/mirror/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
||||
var/turf/T = get_turf(hit_atom)
|
||||
var/datum/thrownthing/D = throwingdatum
|
||||
|
||||
@@ -414,11 +414,11 @@
|
||||
if(safety)
|
||||
to_chat(usr, "<span class='danger'>The safety is still on.</span>")
|
||||
return
|
||||
if(!timing && nuclear_cooldown > world.time)
|
||||
to_chat(usr, "<span class='danger'>[src]'s timer protocols are currently on cooldown, please stand by.</span>")
|
||||
return
|
||||
timing = !timing
|
||||
if(timing)
|
||||
if(nuclear_cooldown > world.time)
|
||||
to_chat(usr, "<span class='danger'>[src]'s timer protocols are currently on cooldown, please stand by.</span>")
|
||||
return
|
||||
previous_level = get_security_level()
|
||||
detonation_timer = world.time + (timer_set * 10)
|
||||
for(var/obj/item/pinpointer/nuke/syndicate/S in GLOB.pinpointer_list)
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
tinfoil_check = FALSE
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/revenant
|
||||
clothes_req = 0
|
||||
clothes_req = NONE
|
||||
action_icon = 'icons/mob/actions/actions_revenant.dmi'
|
||||
action_background_icon_state = "bg_revenant"
|
||||
panel = "Revenant Abilities (Locked)"
|
||||
@@ -135,7 +135,7 @@
|
||||
else
|
||||
name = "[initial(name)] ([cast_amount]E)"
|
||||
|
||||
/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr)
|
||||
/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr, skipcharge = FALSE, silent = FALSE)
|
||||
if(charge_counter < charge_max)
|
||||
return FALSE
|
||||
if(!istype(user)) //Badmins, no. Badmins, don't do it.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/presents)
|
||||
var/obj/effect/proc_holder/spell/targeted/area_teleport/teleport/telespell = new
|
||||
telespell.clothes_req = 0 //santa robes aren't actually magical.
|
||||
telespell.clothes_req = NONE //santa robes aren't actually magical.
|
||||
owner.AddSpell(telespell) //does the station have chimneys? WHO KNOWS!
|
||||
|
||||
/datum/antagonist/santa/proc/give_objective()
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
/datum/syndicate_contract/proc/handleVictimExperience(var/mob/living/M) // They're off to holding - handle the return timer and give some text about what's going on.
|
||||
addtimer(CALLBACK(src, .proc/returnVictim, M), 4 MINUTES) // Ship 'em back - dead or alive... 4 minutes wait.
|
||||
if(M.stat != DEAD) //Even if they weren't the target, we're still treating them the same.
|
||||
M.reagents.add_reagent(/datum/reagent/medicine/omnizine, 20) // Heal them up - gets them out of crit/soft crit.
|
||||
M.reagents.add_reagent(/datum/reagent/medicine/regen_jelly, 20) // Heal them up - gets them out of crit/soft crit. -- now 100% toxinlover friendly!!
|
||||
M.flash_act()
|
||||
M.confused += 10
|
||||
M.blur_eyes(5)
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
dat += " Cooldown:[S.charge_max/10]"
|
||||
dat += " Cost:[cost]<br>"
|
||||
dat += "<i>[S.desc][desc]</i><br>"
|
||||
dat += "[S.clothes_req?"Needs wizard garb":"Can be cast without wizard garb"]<br>"
|
||||
dat += "[S.clothes_req & SPELL_WIZARD_GARB ? "Needs wizard garb" : "Can be cast without wizard garb"]<br>"
|
||||
return dat
|
||||
|
||||
/datum/spellbook_entry/fireball
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
if(holder)
|
||||
holder.update_icon()
|
||||
|
||||
/obj/item/assembly/flash/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/assembly/flash/shield/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
activate()
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/g
|
||||
|
||||
/datum/gas/miasma
|
||||
id = "miasma"
|
||||
specific_heat = 0.00001
|
||||
specific_heat = 20
|
||||
fusion_power = 50
|
||||
name = "Miasma"
|
||||
gas_overlay = "miasma"
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
name = "Summon Servant"
|
||||
desc = "This spell can be used to call your servant, whenever you need it."
|
||||
charge_max = 100
|
||||
clothes_req = 0
|
||||
clothes_req = NONE
|
||||
invocation = "JE VES"
|
||||
invocation_type = "whisper"
|
||||
range = -1
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
/datum/export/gear/combatgloves
|
||||
cost = 80
|
||||
unit_name = "combat gloves"
|
||||
export_types = list(/obj/item/clothing/gloves/combat, /obj/item/clothing/gloves/rapid, /obj/item/clothing/gloves/krav_maga)
|
||||
export_types = list(/obj/item/clothing/gloves/combat, /obj/item/clothing/gloves/fingerless/pugilist/rapid, /obj/item/clothing/gloves/krav_maga)
|
||||
include_subtypes = TRUE
|
||||
|
||||
/datum/export/gear/bonegloves
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
/datum/export/glasswork_lens
|
||||
cost = 1800
|
||||
unit_name = "small glass lens"
|
||||
export_types = list(/obj/item/lens)
|
||||
export_types = list(/obj/item/glasswork/glass_base/lens)
|
||||
|
||||
/datum/export/glasswork_spouty
|
||||
cost = 1200
|
||||
@@ -131,3 +131,27 @@
|
||||
unit_name = "large flask"
|
||||
export_types = list(/obj/item/reagent_containers/glass/beaker/flask/large)
|
||||
include_subtypes = FALSE
|
||||
|
||||
/datum/export/glasswork_teaplate
|
||||
cost = 1200
|
||||
unit_name = "tea gear"
|
||||
export_types = list(/obj/item/tea_plate)
|
||||
include_subtypes = FALSE
|
||||
|
||||
/datum/export/glasswork_teacup
|
||||
cost = 1800
|
||||
unit_name = "tea gear"
|
||||
export_types = list(/obj/item/tea_cup)
|
||||
include_subtypes = FALSE
|
||||
|
||||
/datum/export/glasswork_laserpointer
|
||||
cost = 2600
|
||||
unit_name = "hand made laserpointer"
|
||||
export_types = list(/obj/item/laser_pointer/blue/handmade)
|
||||
include_subtypes = FALSE
|
||||
|
||||
/datum/export/glasswork_glasses
|
||||
cost = 5000
|
||||
unit_name = "hand made glasses"
|
||||
export_types = list(/obj/item/glasswork/glasses)
|
||||
include_subtypes = FALSE
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
/datum/export/weapon/gloves
|
||||
cost = 90
|
||||
unit_name = "star struck gloves"
|
||||
export_types = list(/obj/item/clothing/gloves/rapid)
|
||||
export_types = list(/obj/item/clothing/gloves/fingerless/pugilist/rapid)
|
||||
|
||||
/datum/export/weapon/l6
|
||||
cost = 500
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
/obj/item/instrument/trombone,
|
||||
/obj/item/instrument/recorder,
|
||||
/obj/item/instrument/harmonica,
|
||||
/obj/structure/piano/unanchored)
|
||||
/obj/structure/musician/piano/unanchored)
|
||||
crate_type = /obj/structure/closet/crate/wooden
|
||||
|
||||
/datum/supply_pack/misc/casinocrate
|
||||
|
||||
@@ -704,9 +704,43 @@ GLOBAL_LIST_EMPTY(asset_datums)
|
||||
Insert(initial(D.id), I)
|
||||
return ..()
|
||||
|
||||
/datum/asset/spritesheet/vending
|
||||
name = "vending"
|
||||
|
||||
/datum/asset/spritesheet/vending/register()
|
||||
for(var/k in GLOB.vending_products)
|
||||
var/atom/item = k
|
||||
if(!ispath(item, /atom))
|
||||
continue
|
||||
|
||||
var/icon_file = initial(item.icon)
|
||||
var/icon_state = initial(item.icon_state)
|
||||
var/icon/I
|
||||
|
||||
var/icon_states_list = icon_states(icon_file)
|
||||
if(icon_state in icon_states_list)
|
||||
I = icon(icon_file, icon_state, SOUTH)
|
||||
var/c = initial(item.color)
|
||||
if(!isnull(c) && c != "#FFFFFF")
|
||||
I.Blend(c, ICON_MULTIPLY)
|
||||
else
|
||||
var/icon_states_string
|
||||
for(var/an_icon_state in icon_states_list)
|
||||
if(!icon_states_string)
|
||||
icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])"
|
||||
else
|
||||
icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])"
|
||||
stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]")
|
||||
I = icon('icons/turf/floors.dmi', "", SOUTH)
|
||||
|
||||
var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
|
||||
|
||||
Insert(imgid, I)
|
||||
return ..()
|
||||
|
||||
/datum/asset/simple/genetics
|
||||
assets = list(
|
||||
"dna_discovered.gif" = 'html/dna_discovered.gif',
|
||||
"dna_undiscovered.gif" = 'html/dna_undiscovered.gif',
|
||||
"dna_extra.gif" = 'html/dna_extra.gif'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
resistance_flags = FLAMMABLE
|
||||
max_integrity = 200
|
||||
integrity_failure = 0.4
|
||||
block_priority = BLOCK_PRIORITY_CLOTHING
|
||||
var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var
|
||||
var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS
|
||||
var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect
|
||||
|
||||
@@ -177,11 +177,11 @@
|
||||
/obj/item/clothing/gloves/color/latex/equipped(mob/user, slot)
|
||||
..()
|
||||
if(slot == SLOT_GLOVES)
|
||||
ADD_TRAIT(user, carrytrait, CLOTHING_TRAIT)
|
||||
ADD_TRAIT(user, carrytrait, GLOVE_TRAIT)
|
||||
|
||||
/obj/item/clothing/gloves/color/latex/dropped(mob/user)
|
||||
..()
|
||||
REMOVE_TRAIT(user, carrytrait, CLOTHING_TRAIT)
|
||||
REMOVE_TRAIT(user, carrytrait, GLOVE_TRAIT)
|
||||
|
||||
/obj/item/clothing/gloves/color/latex/nitrile
|
||||
name = "nitrile gloves"
|
||||
|
||||
@@ -11,6 +11,135 @@
|
||||
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
|
||||
strip_mod = 0.9
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist
|
||||
name = "armwraps"
|
||||
desc = "A series of armwraps. Makes you pretty keen to start punching people."
|
||||
icon_state = "armwraps"
|
||||
item_state = "armwraps"
|
||||
body_parts_covered = ARMS
|
||||
cold_protection = ARMS
|
||||
strip_delay = 300 //you can't just yank them off
|
||||
///Extra damage through the punch.
|
||||
var/enhancement = 0 //it's a +0 to your punches because it isn't magical
|
||||
///Main trait added by the gloves to the user on wear.
|
||||
var/inherited_trait = TRAIT_NOGUNS //what are you, dishonoroable?
|
||||
///Secondary trait added by the gloves to the user on wear.
|
||||
var/secondary_trait = TRAIT_FEARLESS //what are you, a coward?
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/equipped(mob/user, slot)
|
||||
. = ..()
|
||||
if(slot == SLOT_GLOVES)
|
||||
if(ishuman(user))
|
||||
var/mob/living/carbon/human/H = user
|
||||
ADD_TRAIT(H, TRAIT_PUGILIST, GLOVE_TRAIT)
|
||||
ADD_TRAIT(H, inherited_trait, GLOVE_TRAIT)
|
||||
ADD_TRAIT(H, secondary_trait, GLOVE_TRAIT)
|
||||
H.dna.species.punchdamagehigh += enhancement
|
||||
H.dna.species.punchdamagelow += enhancement
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/dropped(mob/user)
|
||||
|
||||
REMOVE_TRAIT(user, TRAIT_PUGILIST, GLOVE_TRAIT)
|
||||
REMOVE_TRAIT(user, inherited_trait, GLOVE_TRAIT)
|
||||
REMOVE_TRAIT(user, secondary_trait, GLOVE_TRAIT)
|
||||
if(ishuman(user))
|
||||
var/mob/living/carbon/human/H = user
|
||||
H.dna.species.punchdamagehigh = initial(H.dna.species.punchdamagehigh)
|
||||
H.dna.species.punchdamagelow = initial(H.dna.species.punchdamagelow)
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/chaplain
|
||||
name = "armwraps of unyielding resolve"
|
||||
desc = "A series of armwraps, soaked in holy water. Makes you pretty keen to smite evil magic users."
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
enhancement = 1 //It is not magic that makes you punch harder, but force of will. Trust me.
|
||||
secondary_trait = TRAIT_ANTIMAGIC
|
||||
var/chaplain_spawnable = TRUE
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/chaplain/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE)
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/magic
|
||||
name = "armwraps of mighty fists"
|
||||
desc = "A series of armwraps. Makes you pretty keen to go adventuring and punch dragons."
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF //magic items are harder to damage with energy this is a dnd joke okay?
|
||||
enhancement = 1 //They're +1!
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/hungryghost
|
||||
name = "armwraps of the hungry ghost"
|
||||
desc = "A series of blackened, bloodstained armwraps stitched with strange geometric symbols. Makes you pretty keen to commit horrible acts against the living through bloody carnage."
|
||||
icon_state = "narsiearmwraps"
|
||||
item_state = "narsiearmwraps"
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 35, "rad" = 0, "fire" = 50, "acid" = 50)
|
||||
enhancement = 3
|
||||
secondary_trait = TRAIT_KI_VAMPIRE
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/brassmountain
|
||||
name = "armbands of the brass mountain"
|
||||
desc = "A series of scolding hot brass armbands. Makes you pretty keen to bring the light to the unenlightened through unmitigated violence."
|
||||
icon_state = "ratvararmwraps"
|
||||
item_state = "ratvararmwraps"
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = -10, "energy" = 0, "bomb" = 0, "bio" = 35, "rad" = 0, "fire" = 50, "acid" = 50)
|
||||
enhancement = 4 //The artifice of Ratvar is unmatched except when it is.
|
||||
secondary_trait = TRAIT_STRONG_GRABBER
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid
|
||||
name = "Bands of the North Star"
|
||||
desc = "The armbands of a deadly martial artist. Makes you pretty keen to put an end to evil in an extremely violent manner."
|
||||
icon_state = "rapid"
|
||||
item_state = "rapid"
|
||||
enhancement = 10 //omae wa mou shindeiru
|
||||
var/warcry = "AT"
|
||||
secondary_trait = TRAIT_NOSOFTCRIT //basically extra health
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid/Initialize()
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_NODROP, GLOVE_TRAIT)
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid/Touch(atom/target, proximity = TRUE)
|
||||
if(!isliving(target))
|
||||
return
|
||||
|
||||
var/mob/living/M = loc
|
||||
M.changeNext_move(CLICK_CD_RAPID)
|
||||
if(warcry)
|
||||
M.say("[warcry]", ignore_spam = TRUE, forced = TRUE)
|
||||
|
||||
return FALSE
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid/AltClick(mob/user)
|
||||
var/input = stripped_input(user,"What do you want your battlecry to be? Max length of 6 characters.", ,"", 7)
|
||||
if(input)
|
||||
warcry = input
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid/hug
|
||||
name = "Hugs of the North Star"
|
||||
desc = "The armbands of a humble friend. Makes you pretty keen to go let everyone know how much you appreciate them!"
|
||||
warcry = "owo" //Shouldn't ever come into play
|
||||
enhancement = 0
|
||||
secondary_trait = TRAIT_PACIFISM //You are only here to hug and be friends!
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid/hug/Touch(mob/target, proximity = TRUE)
|
||||
if(!isliving(target))
|
||||
return
|
||||
|
||||
var/mob/living/M = loc
|
||||
|
||||
if(M.a_intent != INTENT_HELP)
|
||||
return FALSE
|
||||
if(target.stat != CONSCIOUS) //Can't hug people who are dying/dead
|
||||
return FALSE
|
||||
else
|
||||
M.changeNext_move(CLICK_CD_RAPID)
|
||||
|
||||
return FALSE
|
||||
|
||||
/obj/item/clothing/gloves/fingerless/pugilist/rapid/hug/AltClick(mob/user)
|
||||
return FALSE
|
||||
|
||||
/obj/item/clothing/gloves/botanic_leather
|
||||
name = "botanist's leather gloves"
|
||||
desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm."
|
||||
@@ -57,54 +186,6 @@
|
||||
resistance_flags = NONE
|
||||
armor = list("melee" = 15, "bullet" = 35, "laser" = 35, "energy" = 20, "bomb" = 35, "bio" = 35, "rad" = 35, "fire" = 0, "acid" = 0)
|
||||
|
||||
/obj/item/clothing/gloves/rapid
|
||||
name = "Gloves of the North Star"
|
||||
desc = "Just looking at these fills you with an urge to beat the shit out of people. Violently."
|
||||
icon_state = "rapid"
|
||||
item_state = "rapid"
|
||||
transfer_prints = TRUE
|
||||
var/warcry = "AT"
|
||||
|
||||
/obj/item/clothing/gloves/rapid/Touch(mob/living/target,proximity = TRUE)
|
||||
if(!istype(target))
|
||||
return
|
||||
|
||||
var/mob/living/M = loc
|
||||
M.changeNext_move(CLICK_CD_RAPID)
|
||||
M.adjustStaminaLoss(-3.5) // used to be -2 with some comment about stamina buffer management but *shrug -hatterhat
|
||||
if(warcry)
|
||||
M.say("[warcry]", ignore_spam = TRUE, forced = "north star warcry")
|
||||
|
||||
.= FALSE
|
||||
|
||||
|
||||
/obj/item/clothing/gloves/rapid/attack_self(mob/user)
|
||||
var/input = stripped_input(user,"What do you want your battlecry to be? Max length of 6 characters.", ,"", 7)
|
||||
if(input)
|
||||
warcry = input
|
||||
|
||||
/obj/item/clothing/gloves/rapid/hug
|
||||
name = "Hugs of the North Star"
|
||||
desc = "Just looking at these fills you with an urge to hug the shit out of people. In a very friendly manner."
|
||||
warcry = "owo" //Shouldn't ever come into play
|
||||
|
||||
/obj/item/clothing/gloves/rapid/hug/Touch(mob/living/target,proximity = TRUE)
|
||||
if(!istype(target))
|
||||
return
|
||||
|
||||
var/mob/living/M = loc
|
||||
|
||||
if(M.a_intent == INTENT_HELP)
|
||||
if(target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH)) //Can't hug people who are dying/dead
|
||||
if(target.on_fire || target.lying) //No spamming extinguishing, helping them up, or other non-hugging/patting help interactions
|
||||
return
|
||||
else
|
||||
M.changeNext_move(CLICK_CD_RAPID)
|
||||
. = FALSE
|
||||
|
||||
/obj/item/clothing/gloves/rapid/hug/attack_self(mob/user)
|
||||
return FALSE
|
||||
|
||||
/obj/item/clothing/gloves/thief
|
||||
name = "black gloves"
|
||||
desc = "Gloves made with completely frictionless, insulated cloth, easier to steal from people with."
|
||||
@@ -115,4 +196,4 @@
|
||||
strip_delay = 80
|
||||
transfer_prints = FALSE
|
||||
strip_mod = 5
|
||||
strip_silence = TRUE
|
||||
strip_silence = TRUE
|
||||
|
||||
@@ -184,14 +184,15 @@
|
||||
/obj/item/clothing/head/wig
|
||||
name = "wig"
|
||||
desc = "A bunch of hair without a head attached."
|
||||
icon_state = ""
|
||||
item_state = "pwig"
|
||||
icon = 'icons/mob/human_face.dmi' // default icon for all hairs
|
||||
icon_state = "hair_vlong"
|
||||
flags_inv = HIDEHAIR
|
||||
color = "#000"
|
||||
var/hair_style = "Very Long Hair"
|
||||
var/hair_color = "#000"
|
||||
|
||||
/obj/item/clothing/head/wig/Initialize(mapload)
|
||||
. = ..()
|
||||
icon_state = "" //Shitty hack that i dont know if it is even neccesary to deal with the vendor stack exception
|
||||
update_icon()
|
||||
|
||||
/obj/item/clothing/head/wig/update_icon_state()
|
||||
@@ -199,14 +200,9 @@
|
||||
if(!S)
|
||||
icon = 'icons/obj/clothing/hats.dmi'
|
||||
icon_state = "pwig"
|
||||
|
||||
/obj/item/clothing/head/wig/update_overlays()
|
||||
. = ..()
|
||||
var/datum/sprite_accessory/S = GLOB.hair_styles_list[hair_style]
|
||||
if(S)
|
||||
var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state, color = hair_color)
|
||||
M.appearance_flags |= RESET_COLOR
|
||||
. += M
|
||||
else
|
||||
icon = S.icon
|
||||
icon_state = S.icon_state
|
||||
|
||||
/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
|
||||
. = list()
|
||||
@@ -216,12 +212,12 @@
|
||||
return
|
||||
var/mutable_appearance/M = mutable_appearance(S.icon, S.icon_state,layer = -HAIR_LAYER)
|
||||
M.appearance_flags |= RESET_COLOR
|
||||
M.color = hair_color
|
||||
M.color = color
|
||||
. += M
|
||||
|
||||
/obj/item/clothing/head/wig/random/Initialize(mapload)
|
||||
hair_style = pick(GLOB.hair_styles_list - "Bald") //Don't want invisible wig
|
||||
hair_color = "#[random_short_color()]"
|
||||
color = "#[random_short_color()]"
|
||||
. = ..()
|
||||
|
||||
/obj/item/clothing/head/bronze
|
||||
|
||||
@@ -423,6 +423,10 @@
|
||||
heat_protection = HEAD //Uncomment to enable firesuit protection
|
||||
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/wizard/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/spellcasting, SPELL_WIZARD_HAT, ITEM_SLOT_HEAD)
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/wizard
|
||||
icon_state = "hardsuit-wiz"
|
||||
name = "gem-encrusted hardsuit"
|
||||
@@ -437,9 +441,10 @@
|
||||
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/wizard
|
||||
mutantrace_variation = STYLE_DIGITIGRADE
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/wizard/Initialize()
|
||||
/obj/item/clothing/suit/space/hardsuit/wizard/ComponentInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/anti_magic, TRUE, FALSE, FALSE, ITEM_SLOT_OCLOTHING, INFINITY, FALSE)
|
||||
AddElement(/datum/element/spellcasting, SPELL_WIZARD_ROBE, ITEM_SLOT_OCLOTHING)
|
||||
|
||||
//Medical hardsuit
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/medical
|
||||
@@ -758,7 +763,13 @@
|
||||
if(!allowed)
|
||||
allowed = GLOB.advanced_hardsuit_allowed
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(current_charges > 0)
|
||||
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
|
||||
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + current_charges
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
recharge_cooldown = world.time + recharge_delay
|
||||
if(current_charges > 0)
|
||||
var/datum/effect_system/spark_spread/s = new
|
||||
@@ -772,8 +783,8 @@
|
||||
owner.visible_message("[owner]'s shield overloads!")
|
||||
shield_state = "broken"
|
||||
owner.update_inv_wear_suit()
|
||||
return 1
|
||||
return 0
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
|
||||
@@ -455,7 +455,8 @@ Contains:
|
||||
armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0)
|
||||
strip_delay = 65
|
||||
|
||||
/obj/item/clothing/suit/space/fragile/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/clothing/suit/space/fragile/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
. = ..()
|
||||
if(!torn && prob(50) && damage >= 5)
|
||||
to_chat(owner, "<span class='warning'>[src] tears from the damage, breaking the air-tight seal!</span>")
|
||||
clothing_flags &= ~STOPSPRESSUREDAMAGE
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/obj/item/clothing/suit
|
||||
icon = 'icons/obj/clothing/suits.dmi'
|
||||
name = "suit"
|
||||
block_priority = BLOCK_PRIORITY_WEAR_SUIT
|
||||
var/fire_resist = T0C+100
|
||||
allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman)
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
|
||||
@@ -173,12 +173,13 @@
|
||||
armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 50, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
var/hit_reflect_chance = 40
|
||||
var/list/protected_zones = list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN)
|
||||
|
||||
/obj/item/clothing/suit/armor/laserproof/IsReflect(def_zone)
|
||||
if(!(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN))) //If not shot where ablative is covering you, you don't get the reflection bonus!
|
||||
return 0
|
||||
if (prob(hit_reflect_chance))
|
||||
return 1
|
||||
/obj/item/clothing/suit/armor/laserproof/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(def_zone in protected_zones)
|
||||
if(prob(hit_reflect_chance))
|
||||
return BLOCK_SHOULD_REDIRECT | BLOCK_REDIRECTED | BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL
|
||||
return ..()
|
||||
|
||||
/obj/item/clothing/suit/armor/vest/det_suit
|
||||
name = "detective's armor vest"
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
|
||||
actions_types = list(/datum/action/item_action/toggle)
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
hit_reaction_chance = 50 // Only on the chest yet blocks all attacks?
|
||||
rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE
|
||||
var/hit_reaction_chance = 50
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/attack_self(mob/user)
|
||||
active = !(active)
|
||||
@@ -62,6 +62,14 @@
|
||||
item_state = "reactiveoff"
|
||||
reactivearmor_cooldown = world.time + 200
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(!active)
|
||||
return BLOCK_NONE
|
||||
return block_action(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/proc/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
return BLOCK_NONE
|
||||
|
||||
//When the wearer gets hit, this armor will teleport the user a short distance away (to safety or to more danger, no one knows. That's the fun of it!)
|
||||
/obj/item/clothing/suit/armor/reactive/teleport
|
||||
name = "reactive teleport armor"
|
||||
@@ -71,18 +79,15 @@
|
||||
var/rad_amount_before = 120
|
||||
reactivearmor_cooldown_duration = 100
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/teleport/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
. = FALSE
|
||||
if(!active)
|
||||
return
|
||||
/obj/item/clothing/suit/armor/reactive/teleport/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(prob(hit_reaction_chance))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
if(world.time < reactivearmor_cooldown)
|
||||
owner.visible_message("<span class='danger'>The reactive teleport system is still recharging! It fails to teleport [H]!</span>")
|
||||
return
|
||||
owner.visible_message("<span class='danger'>The reactive teleport system flings [H] clear of [attack_text], shutting itself off in the process!</span>")
|
||||
playsound(get_turf(owner),'sound/magic/blink.ogg', 100, 1)
|
||||
var/list/turfs = new/list()
|
||||
playsound(get_turf(owner), 'sound/magic/blink.ogg', 100, 1)
|
||||
var/list/turfs = new
|
||||
var/turf/old = get_turf(src)
|
||||
for(var/turf/T in orange(tele_range, H))
|
||||
if(T.density)
|
||||
@@ -101,7 +106,9 @@
|
||||
radiation_pulse(old, rad_amount_before)
|
||||
radiation_pulse(src, rad_amount)
|
||||
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
|
||||
return TRUE
|
||||
block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_PASSTHROUGH
|
||||
return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT | BLOCK_TARGET_DODGED
|
||||
return BLOCK_NONE
|
||||
|
||||
//Fire
|
||||
|
||||
@@ -109,9 +116,7 @@
|
||||
name = "reactive incendiary armor"
|
||||
desc = "An experimental suit of armor with a reactive sensor array rigged to a flame emitter. For the stylish pyromaniac."
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/fire/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(!active)
|
||||
return 0
|
||||
/obj/item/clothing/suit/armor/reactive/fire/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(prob(hit_reaction_chance))
|
||||
if(world.time < reactivearmor_cooldown)
|
||||
owner.visible_message("<span class='danger'>The reactive incendiary armor on [owner] activates, but fails to send out flames as it is still recharging its flame jets!</span>")
|
||||
@@ -124,8 +129,8 @@
|
||||
C.IgniteMob()
|
||||
owner.fire_stacks = -20
|
||||
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
|
||||
return 1
|
||||
return 0
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
//Stealth
|
||||
|
||||
@@ -134,9 +139,7 @@
|
||||
reactivearmor_cooldown_duration = 65
|
||||
desc = "An experimental suit of armor that renders the wearer invisible on detection of imminent harm, and creates a decoy that runs away from the owner. You can't fight what you can't see."
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/stealth/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(!active)
|
||||
return 0
|
||||
/obj/item/clothing/suit/armor/reactive/stealth/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(prob(hit_reaction_chance))
|
||||
if(world.time < reactivearmor_cooldown)
|
||||
owner.visible_message("<span class='danger'>The reactive stealth system on [owner] activates, but is still recharging its holographic emitters!</span>")
|
||||
@@ -150,7 +153,8 @@
|
||||
spawn(40)
|
||||
owner.alpha = initial(owner.alpha)
|
||||
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
|
||||
return 1
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
//Tesla
|
||||
|
||||
@@ -175,9 +179,7 @@
|
||||
if(slot_flags & slotdefine2slotbit(slot)) //Was equipped to a valid slot for this item?
|
||||
user.flags_1 |= TESLA_IGNORE_1
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/tesla/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(!active)
|
||||
return FALSE
|
||||
/obj/item/clothing/suit/armor/reactive/tesla/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(prob(hit_reaction_chance))
|
||||
if(world.time < reactivearmor_cooldown)
|
||||
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread
|
||||
@@ -196,7 +198,8 @@
|
||||
M.adjustFireLoss(legacy_dmg)
|
||||
playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1)
|
||||
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
|
||||
return TRUE
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL
|
||||
return NONE
|
||||
|
||||
//Repulse
|
||||
|
||||
@@ -205,9 +208,7 @@
|
||||
reactivearmor_cooldown_duration = 20
|
||||
desc = "An experimental suit of armor that violently throws back attackers."
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/repulse/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(!active)
|
||||
return 0
|
||||
/obj/item/clothing/suit/armor/reactive/repulse/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(prob(hit_reaction_chance))
|
||||
if(world.time < reactivearmor_cooldown)
|
||||
owner.visible_message("<span class='danger'>The repulse generator is still recharging!</span>")
|
||||
@@ -234,7 +235,8 @@
|
||||
AM.throw_at(throwtarget,10,1)
|
||||
safety--
|
||||
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
|
||||
return 1
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/table
|
||||
name = "reactive table armor"
|
||||
@@ -242,9 +244,7 @@
|
||||
desc = "If you can't beat the memes, embrace them."
|
||||
var/tele_range = 10
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/table/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(!active)
|
||||
return 0
|
||||
/obj/item/clothing/suit/armor/reactive/table/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(prob(hit_reaction_chance))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
if(world.time < reactivearmor_cooldown)
|
||||
@@ -270,8 +270,8 @@
|
||||
H.forceMove(picked)
|
||||
new /obj/structure/table(get_turf(owner))
|
||||
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
|
||||
return 1
|
||||
return 0
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/obj/item/clothing/suit/armor/reactive/table/emp_act()
|
||||
return
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
equip_delay_other = 50
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
dog_fashion = /datum/dog_fashion/head/blue_wizard
|
||||
var/magic_flags = SPELL_WIZARD_HAT
|
||||
|
||||
/obj/item/clothing/head/wizard/ComponentInitialize()
|
||||
. = ..()
|
||||
if(magic_flags)
|
||||
AddElement(/datum/element/spellcasting, magic_flags, ITEM_SLOT_HEAD)
|
||||
|
||||
/obj/item/clothing/head/wizard/red
|
||||
name = "red wizard hat"
|
||||
@@ -36,7 +42,7 @@
|
||||
permeability_coefficient = 1
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
resistance_flags = FLAMMABLE
|
||||
dog_fashion = /datum/dog_fashion/head/blue_wizard
|
||||
magic_flags = NONE
|
||||
|
||||
/obj/item/clothing/head/wizard/marisa
|
||||
name = "witch hat"
|
||||
@@ -50,6 +56,7 @@
|
||||
icon_state = "magus"
|
||||
item_state = "magus"
|
||||
dog_fashion = null
|
||||
magic_flags = SPELL_WIZARD_HAT|SPELL_CULT_HELMET
|
||||
|
||||
/obj/item/clothing/head/wizard/santa
|
||||
name = "Santa's hat"
|
||||
@@ -72,6 +79,12 @@
|
||||
strip_delay = 50
|
||||
equip_delay_other = 50
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
var/magic_flags = SPELL_WIZARD_ROBE
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/ComponentInitialize()
|
||||
. = ..()
|
||||
if(magic_flags)
|
||||
AddElement(/datum/element/spellcasting, magic_flags, ITEM_SLOT_OCLOTHING)
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/red
|
||||
name = "red wizard robe"
|
||||
@@ -102,13 +115,14 @@
|
||||
desc = "A set of armored robes that seem to radiate a dark power."
|
||||
icon_state = "magusblue"
|
||||
item_state = "magusblue"
|
||||
magic_flags = SPELL_WIZARD_ROBE|SPELL_CULT_ARMOR
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/magusred
|
||||
name = "\improper Magus robe"
|
||||
desc = "A set of armored robes that seem to radiate a dark power."
|
||||
icon_state = "magusred"
|
||||
item_state = "magusred"
|
||||
|
||||
magic_flags = SPELL_WIZARD_ROBE|SPELL_CULT_ARMOR
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/santa
|
||||
name = "Santa's suit"
|
||||
@@ -117,33 +131,27 @@
|
||||
item_state = "santa"
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/fake
|
||||
name = "wizard robe"
|
||||
desc = "A rather dull blue robe meant to mimick real wizard robes."
|
||||
icon_state = "wizard-fake"
|
||||
item_state = "wizrobe"
|
||||
gas_transfer_coefficient = 1
|
||||
permeability_coefficient = 1
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
resistance_flags = FLAMMABLE
|
||||
magic_flags = NONE
|
||||
|
||||
/obj/item/clothing/head/wizard/marisa/fake
|
||||
name = "witch hat"
|
||||
desc = "Strange-looking hat-wear, makes you want to cast fireballs."
|
||||
icon_state = "marisa"
|
||||
gas_transfer_coefficient = 1
|
||||
permeability_coefficient = 1
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
resistance_flags = FLAMMABLE
|
||||
magic_flags = NONE
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/marisa/fake
|
||||
name = "witch robe"
|
||||
desc = "Magic is all about the spell power, ZE!"
|
||||
icon_state = "marisa"
|
||||
item_state = "marisarobe"
|
||||
gas_transfer_coefficient = 1
|
||||
permeability_coefficient = 1
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
resistance_flags = FLAMMABLE
|
||||
magic_flags = NONE
|
||||
|
||||
/obj/item/clothing/suit/wizrobe/paper
|
||||
name = "papier-mache robe" // no non-latin characters!
|
||||
@@ -198,6 +206,10 @@
|
||||
slowdown = 0
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
|
||||
/obj/item/clothing/suit/space/hardsuit/shielded/wizard/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/spellcasting, SPELL_WIZARD_HAT, ITEM_SLOT_HEAD)
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard
|
||||
name = "battlemage helmet"
|
||||
desc = "A suitably impressive helmet.."
|
||||
@@ -209,6 +221,10 @@
|
||||
actions_types = null //No inbuilt light
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/spellcasting, SPELL_WIZARD_ROBE, ITEM_SLOT_OCLOTHING)
|
||||
|
||||
/obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard/attack_self(mob/user)
|
||||
return
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
name = "under"
|
||||
body_parts_covered = CHEST|GROIN|LEGS|ARMS
|
||||
permeability_coefficient = 0.9
|
||||
block_priority = BLOCK_PRIORITY_UNIFORM
|
||||
slot_flags = ITEM_SLOT_ICLOTHING
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
mutantrace_variation = STYLE_DIGITIGRADE
|
||||
|
||||
@@ -76,9 +76,11 @@
|
||||
name = "ancient jumpsuit"
|
||||
desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade."
|
||||
|
||||
/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
owner.forcesay(GLOB.hit_appends)
|
||||
return 0
|
||||
/obj/item/clothing/under/color/grey/glorf/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
. = ..()
|
||||
if(ishuman(owner))
|
||||
var/mob/living/carbon/human/H = owner
|
||||
H.forcesay(GLOB.hit_appends)
|
||||
|
||||
/obj/item/clothing/under/color/blue
|
||||
name = "blue jumpsuit"
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
icon_state = "explorer_envirosuit"
|
||||
item_state = "explorer_envirosuit"
|
||||
|
||||
|
||||
/obj/item/clothing/under/plasmaman/chef
|
||||
name = "chef's plasma envirosuit"
|
||||
desc = "A white plasmaman envirosuit designed for cullinary practices. One might question why a member of a species that doesn't need to eat would become a chef."
|
||||
@@ -59,6 +58,8 @@
|
||||
icon_state = "captain_envirosuit"
|
||||
item_state = "captain_envirosuit"
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
|
||||
sensor_mode = SENSOR_COORDS
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/plasmaman/mime
|
||||
name = "mime envirosuit"
|
||||
@@ -85,4 +86,4 @@
|
||||
H.visible_message("<span class='warning'>[H]'s suit spews out a tonne of space lube!</span>","<span class='warning'>Your suit spews out a tonne of space lube!</span>")
|
||||
H.ExtinguishMob()
|
||||
new /obj/effect/particle_effect/foam(loc) //Truely terrifying.
|
||||
return FALSE
|
||||
return FALSE
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
icon_state = "security_envirosuit"
|
||||
item_state = "security_envirosuit"
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
|
||||
sensor_mode = SENSOR_COORDS
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/plasmaman/security/warden
|
||||
name = "warden plasma envirosuit"
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
name = "sensible suitskirt"
|
||||
icon_state = "red_suit_skirt"
|
||||
body_parts_covered = CHEST|GROIN|ARMS
|
||||
can_adjust = FALSE
|
||||
fitted = FEMALE_UNIFORM_TOP
|
||||
|
||||
/obj/item/clothing/under/rank/civilian/curator/treasure_hunter
|
||||
@@ -31,5 +30,4 @@
|
||||
min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
|
||||
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
|
||||
max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT
|
||||
can_adjust = FALSE
|
||||
resistance_flags = NONE
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
icon_state = "director_skirt"
|
||||
item_state = "lb_suit"
|
||||
body_parts_covered = CHEST|GROIN|ARMS
|
||||
can_adjust = FALSE
|
||||
fitted = FEMALE_UNIFORM_TOP
|
||||
|
||||
/obj/item/clothing/under/rank/rnd/research_director/alt
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
/*
|
||||
* Security
|
||||
*/
|
||||
/obj/item/clothing/under/rank/security
|
||||
strip_delay = 50
|
||||
alt_covers_chest = TRUE
|
||||
sensor_mode = SENSOR_COORDS
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/rank/security/officer
|
||||
name = "security jumpsuit"
|
||||
@@ -15,10 +20,6 @@
|
||||
icon_state = "rsecurity"
|
||||
item_state = "r_suit"
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30)
|
||||
strip_delay = 50
|
||||
alt_covers_chest = TRUE
|
||||
sensor_mode = SENSOR_COORDS
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/rank/security/officer/grey
|
||||
name = "grey security jumpsuit"
|
||||
@@ -66,10 +67,6 @@
|
||||
icon_state = "rwarden"
|
||||
item_state = "r_suit"
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30)
|
||||
strip_delay = 50
|
||||
alt_covers_chest = TRUE
|
||||
sensor_mode = 3
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/rank/security/warden/grey
|
||||
name = "grey security suit"
|
||||
@@ -103,10 +100,6 @@
|
||||
icon_state = "detective"
|
||||
item_state = "det"
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 30)
|
||||
strip_delay = 50
|
||||
alt_covers_chest = TRUE
|
||||
sensor_mode = 3
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/rank/security/detective/skirt
|
||||
name = "detective's suitskirt"
|
||||
@@ -122,7 +115,6 @@
|
||||
desc = "A hard-boiled private investigator's grey suit, complete with tie clip."
|
||||
icon_state = "greydet"
|
||||
item_state = "greydet"
|
||||
alt_covers_chest = TRUE
|
||||
|
||||
/obj/item/clothing/under/rank/security/detective/grey/skirt
|
||||
name = "noir suitskirt"
|
||||
@@ -144,9 +136,6 @@
|
||||
item_state = "r_suit"
|
||||
armor = list("melee" = 10, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
|
||||
strip_delay = 60
|
||||
alt_covers_chest = TRUE
|
||||
sensor_mode = 3
|
||||
random_sensor = FALSE
|
||||
|
||||
/obj/item/clothing/under/rank/security/head_of_security/skirt
|
||||
name = "head of security's jumpskirt"
|
||||
@@ -183,7 +172,6 @@
|
||||
name = "head of security's formal uniform"
|
||||
icon_state = "hosblueclothes"
|
||||
item_state = "hosblueclothes"
|
||||
alt_covers_chest = TRUE
|
||||
|
||||
/obj/item/clothing/under/rank/security/head_of_security/parade
|
||||
name = "head of security's parade uniform"
|
||||
@@ -198,4 +186,3 @@
|
||||
icon_state = "hos_parade_fem"
|
||||
item_state = "r_suit"
|
||||
fitted = FEMALE_UNIFORM_TOP
|
||||
can_adjust = FALSE
|
||||
|
||||
@@ -393,17 +393,13 @@
|
||||
|
||||
/datum/spacevine_controller/vv_get_dropdown()
|
||||
. = ..()
|
||||
. += "---"
|
||||
.["Delete Vines"] = "?_src_=[REF(src)];[HrefToken()];purge_vines=1"
|
||||
VV_DROPDOWN_OPTION(VV_HK_SPACEVINE_PURGE, "Delete Vines")
|
||||
|
||||
/datum/spacevine_controller/Topic(href, href_list)
|
||||
if(..() || !check_rights(R_ADMIN, FALSE) || !usr.client.holder.CheckAdminHref(href, href_list))
|
||||
return
|
||||
|
||||
if(href_list["purge_vines"])
|
||||
if(alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", "Yes", "No") != "Yes")
|
||||
return
|
||||
DeleteVines()
|
||||
/datum/spacevine_controller/vv_do_topic(href_list)
|
||||
. = ..()
|
||||
if(href_list[VV_HK_SPACEVINE_PURGE])
|
||||
if(alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", "Yes", "No") == "Yes")
|
||||
DeleteVines()
|
||||
|
||||
/datum/spacevine_controller/proc/DeleteVines() //this is kill
|
||||
QDEL_LIST(vines) //this will also qdel us
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
var/spell_improved = FALSE
|
||||
for(var/obj/effect/proc_holder/spell/S in L.mind.spell_list)
|
||||
if(S.clothes_req)
|
||||
S.clothes_req = 0
|
||||
S.clothes_req = NONE
|
||||
spell_improved = TRUE
|
||||
if(spell_improved)
|
||||
to_chat(L, "<span class='notice'>You suddenly feel like you never needed those garish robes in the first place...</span>")
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
. = ..()
|
||||
saber_color = "red"
|
||||
|
||||
/obj/item/holo/esword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
if(active)
|
||||
/obj/item/holo/esword/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
if(!active)
|
||||
return ..()
|
||||
return 0
|
||||
return ..()
|
||||
|
||||
/obj/item/holo/esword/attack(target as mob, mob/user as mob)
|
||||
..()
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
lifespan = 30
|
||||
endurance = 25
|
||||
mutatelist = list()
|
||||
genes = list(/datum/plant_gene/trait/glow/white, /datum/plant_gene/trait/noreact, /datum/plant_gene/trait/repeated_harvest)
|
||||
genes = list(/datum/plant_gene/trait/glow/white, /datum/plant_gene/trait/repeated_harvest)
|
||||
reagents_add = list(/datum/reagent/uranium = 0.25, /datum/reagent/iodine = 0.2, /datum/reagent/consumable/nutriment/vitamin = 0.04, /datum/reagent/consumable/nutriment = 0.1)
|
||||
rarity = 20
|
||||
|
||||
|
||||
@@ -492,9 +492,6 @@
|
||||
return
|
||||
var/turf/T = get_turf(src)
|
||||
reagents.chem_temp = 1000
|
||||
//Disable seperated contents when the grenade primes
|
||||
if (seed.get_gene(/datum/plant_gene/trait/noreact))
|
||||
DISABLE_BITFIELD(reagents.reagents_holder_flags, NO_REACT)
|
||||
reagents.handle_reactions()
|
||||
log_game("Coconut bomb detonation at [AREACOORD(T)], location [loc]")
|
||||
qdel(src)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
mutatelist = list()
|
||||
reagents_add = list(/datum/reagent/medicine/salbutamol = 0.05, /datum/reagent/drug/nicotine = 0.08, /datum/reagent/consumable/nutriment = 0.03)
|
||||
rarity = 20
|
||||
genes = list(/datum/plant_gene/trait/smoke) //get it? because you smoke tobacco? i'm hilarious.
|
||||
|
||||
/obj/item/reagent_containers/food/snacks/grown/tobacco/space
|
||||
seed = /obj/item/seeds/tobacco/space
|
||||
|
||||
@@ -216,6 +216,10 @@
|
||||
name = "Liquid Contents"
|
||||
examine_line = "<span class='info'>It has a lot of liquid contents inside.</span>"
|
||||
|
||||
/datum/plant_gene/trait/squash/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C)
|
||||
// Squash the plant on slip.
|
||||
G.squash(C)
|
||||
|
||||
/datum/plant_gene/trait/slip
|
||||
// Makes plant slippery, unless it has a grown-type trash. Then the trash gets slippery.
|
||||
// Applies other trait effects (teleporting, etc) to the target by on_slip.
|
||||
@@ -301,6 +305,9 @@
|
||||
rate = 0.04
|
||||
glow_color = "#AAD84B"
|
||||
|
||||
/datum/plant_gene/trait/glow/shadow/glow_power(obj/item/seeds/S)
|
||||
return -max(S.potency*(rate*0.2), 0.2)
|
||||
|
||||
/datum/plant_gene/trait/glow/white
|
||||
name = "White Bioluminescence"
|
||||
glow_color = "#FFFFFF"
|
||||
@@ -361,20 +368,6 @@
|
||||
new /obj/effect/decal/cleanable/molten_object(T) //Leave a pile of goo behind for dramatic effect...
|
||||
qdel(G)
|
||||
|
||||
|
||||
/datum/plant_gene/trait/noreact
|
||||
// Makes plant reagents not react until squashed.
|
||||
name = "Separated Chemicals"
|
||||
|
||||
/datum/plant_gene/trait/noreact/on_new(obj/item/reagent_containers/food/snacks/grown/G, newloc)
|
||||
..()
|
||||
ENABLE_BITFIELD(G.reagents.reagents_holder_flags, NO_REACT)
|
||||
|
||||
/datum/plant_gene/trait/noreact/on_squash(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
|
||||
DISABLE_BITFIELD(G.reagents.reagents_holder_flags, NO_REACT)
|
||||
G.reagents.handle_reactions()
|
||||
|
||||
|
||||
/datum/plant_gene/trait/maxchem
|
||||
// 2x to max reagents volume.
|
||||
name = "Densified Chemicals"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/proc/path_to_instrument_ids(path)
|
||||
if(!ispath(path))
|
||||
path = text2path(path)
|
||||
if(!ispath(path))
|
||||
return
|
||||
if(!ispath(path, /datum/instrument))
|
||||
return
|
||||
. = list()
|
||||
for(var/i in typesof(path))
|
||||
var/datum/instrument/I = i
|
||||
var/init_id = initial(I.id)
|
||||
if(init_id)
|
||||
. |= init_id
|
||||
|
||||
/// Get all non admin_only instruments.
|
||||
/proc/get_allowed_instrument_ids()
|
||||
. = list()
|
||||
for(var/id in SSinstruments.instrument_data)
|
||||
var/datum/instrument/I = SSinstruments.instrument_data[id]
|
||||
if(!I.admin_only)
|
||||
. += I.id
|
||||
|
||||
/datum/instrument
|
||||
/// Name of the instrument
|
||||
var/name = "Generic instrument"
|
||||
/// Uniquely identifies this instrument so runtime changes are possible as opposed to paths. If this is unset, things will use path instead.
|
||||
var/id
|
||||
/// Category
|
||||
var/category = "Unsorted"
|
||||
/// Used for categorization subtypes
|
||||
var/abstract_type = /datum/instrument
|
||||
/// Write here however many samples, follow this syntax: "%note num%"='%sample file%' eg. "27"='synthesizer/e2.ogg'. Key must never be lower than 0 and higher than 127
|
||||
var/list/real_samples
|
||||
/// assoc list key = /datum/instrument_key. do not fill this yourself!
|
||||
var/list/samples
|
||||
/// See __DEFINES/flags/instruments.dm
|
||||
var/instrument_flags = NONE
|
||||
/// For legacy instruments, the path to our notes
|
||||
var/legacy_instrument_path
|
||||
/// For legacy instruments, our file extension
|
||||
var/legacy_instrument_ext
|
||||
/// What songs are using us
|
||||
var/list/datum/song/songs_using = list()
|
||||
/// Don't touch this
|
||||
var/static/HIGHEST_KEY = 127
|
||||
/// Don't touch this x2
|
||||
var/static/LOWEST_KEY = 0
|
||||
/// Oh no - For truly troll instruments.
|
||||
var/admin_only = FALSE
|
||||
/// Volume multiplier. Synthesized instruments are quite loud and I don't like to cut off potential detail via editing. (someone correct me if this isn't a thing)
|
||||
var/volume_multiplier = 1/3
|
||||
|
||||
/datum/instrument/New()
|
||||
if(isnull(id))
|
||||
id = "[type]"
|
||||
|
||||
/datum/instrument/proc/Initialize()
|
||||
if(CHECK_BITFIELD(instrument_flags, INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE))
|
||||
return
|
||||
calculate_samples()
|
||||
|
||||
/datum/instrument/proc/ready()
|
||||
if(CHECK_BITFIELD(instrument_flags, INSTRUMENT_LEGACY))
|
||||
return legacy_instrument_path && legacy_instrument_ext
|
||||
else if(CHECK_BITFIELD(instrument_flags, INSTRUMENT_DO_NOT_AUTOSAMPLE))
|
||||
return length(samples)
|
||||
return (length(samples) >= 128)
|
||||
|
||||
/datum/instrument/Destroy()
|
||||
SSinstruments.instrument_data -= id
|
||||
for(var/i in songs_using)
|
||||
var/datum/song/S = i
|
||||
S.set_instrument(null)
|
||||
real_samples = null
|
||||
samples = null
|
||||
songs_using = null
|
||||
return ..()
|
||||
|
||||
/datum/instrument/proc/calculate_samples()
|
||||
if(!length(real_samples))
|
||||
CRASH("No real samples defined for [id] [type] on calculate_samples() call.")
|
||||
var/list/real_keys = list()
|
||||
samples = list()
|
||||
for(var/key in real_samples)
|
||||
real_keys += text2num(key)
|
||||
sortTim(real_keys, /proc/cmp_numeric_asc, associative = FALSE)
|
||||
|
||||
for(var/i in 1 to (length(real_keys) - 1))
|
||||
var/from_key = real_keys[i]
|
||||
var/to_key = real_keys[i+1]
|
||||
var/sample1 = real_samples[num2text(from_key)]
|
||||
var/sample2 = real_samples[num2text(to_key)]
|
||||
var/pivot = FLOOR((from_key + to_key) / 2, 1) //original code was a round but I replaced it because that's effectively a floor, thanks Baystation! who knows what was intended.
|
||||
for(var/key in from_key to pivot)
|
||||
samples[num2text(key)] = new /datum/instrument_key(sample1, key, key - from_key)
|
||||
for(var/key in (pivot + 1) to to_key)
|
||||
samples[num2text(key)] = new /datum/instrument_key(sample2, key, key - to_key)
|
||||
|
||||
// Fill in 0 to first key and last key to 127
|
||||
var/first_key = real_keys[1]
|
||||
var/last_key = real_keys[length(real_keys)]
|
||||
var/first_sample = real_samples[num2text(first_key)]
|
||||
var/last_sample = real_samples[num2text(last_key)]
|
||||
for(var/key in LOWEST_KEY to (first_key - 1))
|
||||
samples[num2text(key)] = new /datum/instrument_key(first_sample, key, key - first_key)
|
||||
for(var/key in last_key to HIGHEST_KEY)
|
||||
samples[num2text(key)] = new /datum/instrument_key(last_sample, key, key - last_key)
|
||||
@@ -0,0 +1,20 @@
|
||||
/datum/instrument_key
|
||||
var/key //1 to 127
|
||||
var/sample //file
|
||||
var/frequency //frequency generated
|
||||
var/deviation //deviation up/down towards pivot from sample (??)
|
||||
|
||||
/datum/instrument_key/New(sample = src.sample, key = src.key, deviation = src.deviation, frequency = src.frequency)
|
||||
src.sample = sample
|
||||
src.key = key
|
||||
src.deviation = deviation
|
||||
src.frequency = frequency
|
||||
if(!frequency && deviation)
|
||||
calculate()
|
||||
|
||||
/datum/instrument_key/proc/calculate()
|
||||
if(!deviation)
|
||||
CRASH("Invalid calculate call: No deviation or sample in instrument_key")
|
||||
#define KEY_TWELTH (1/12)
|
||||
frequency = 2 ** (KEY_TWELTH * deviation)
|
||||
#undef KEY_TWELTH
|
||||
@@ -0,0 +1,26 @@
|
||||
/datum/instrument/brass
|
||||
name = "Generic brass instrument"
|
||||
category = "Brass"
|
||||
abstract_type = /datum/instrument/brass
|
||||
|
||||
/datum/instrument/brass/crisis_section
|
||||
name = "Crisis Brass Section"
|
||||
id = "crbrass"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg')
|
||||
|
||||
/datum/instrument/brass/crisis_trombone
|
||||
name = "Crisis Trombone"
|
||||
id = "crtrombone"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg')
|
||||
|
||||
/datum/instrument/brass/crisis_trumpet
|
||||
name = "Crisis Trumpet"
|
||||
id = "crtrumpet"
|
||||
real_samples = list("60"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg')
|
||||
@@ -0,0 +1,31 @@
|
||||
/datum/instrument/chromatic
|
||||
name = "Generic chromatic percussion instrument"
|
||||
category = "Chromatic percussion"
|
||||
abstract_type = /datum/instrument/chromatic
|
||||
|
||||
/datum/instrument/chromatic/vibraphone1
|
||||
name = "Crisis Vibraphone"
|
||||
id = "crvibr"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg')
|
||||
|
||||
/datum/instrument/chromatic/musicbox1
|
||||
name = "SGM Music Box"
|
||||
id = "sgmmbox"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg')
|
||||
|
||||
/datum/instrument/chromatic/fluid_celeste
|
||||
name = "FluidR3 Celeste"
|
||||
id = "r3celeste"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg')
|
||||
@@ -0,0 +1,25 @@
|
||||
/datum/instrument/fun
|
||||
name = "Generic Fun Instrument"
|
||||
category = "Fun"
|
||||
abstract_type = /datum/instrument/fun
|
||||
|
||||
/datum/instrument/fun/honk
|
||||
name = "!!HONK!!"
|
||||
id = "honk"
|
||||
real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven
|
||||
|
||||
/datum/instrument/fun/signal
|
||||
name = "Ping"
|
||||
id = "ping"
|
||||
real_samples = list("79"='sound/machines/ping.ogg')
|
||||
|
||||
/datum/instrument/fun/chime
|
||||
name = "Chime"
|
||||
id = "chime"
|
||||
real_samples = list("79"='sound/machines/chime.ogg')
|
||||
|
||||
/datum/instrument/fun/nya
|
||||
name = "NYA NYA NYA"
|
||||
id = "nyanyanya"
|
||||
real_samples = list("79" = 'modular_citadel/sound/voice/nya.ogg')
|
||||
admin_only = TRUE
|
||||
@@ -0,0 +1,36 @@
|
||||
/datum/instrument/guitar
|
||||
name = "Generic guitar-like instrument"
|
||||
category = "Guitar"
|
||||
abstract_type = /datum/instrument/guitar
|
||||
|
||||
/datum/instrument/guitar/steel_crisis
|
||||
name = "Crisis Steel String Guitar"
|
||||
id = "csteelgt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg')
|
||||
|
||||
/datum/instrument/guitar/nylon_crisis
|
||||
name = "Crisis Nylon String Guitar"
|
||||
id = "cnylongt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg')
|
||||
|
||||
/datum/instrument/guitar/clean_crisis
|
||||
name = "Crisis Clean Guitar"
|
||||
id = "ccleangt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg')
|
||||
|
||||
/datum/instrument/guitar/muted_crisis
|
||||
name = "Crisis Muted Guitar"
|
||||
id = "cmutedgt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg')
|
||||
@@ -0,0 +1,86 @@
|
||||
//THESE ARE HARDCODED INSTRUMENT SAMPLES.
|
||||
//SONGS WILL BE AUTOMATICALLY SWITCHED TO LEGACY MODE IF THEY USE THIS KIND OF INSTRUMENT!
|
||||
//I'd prefer these stayed. They sound different from the mechanical synthesis of synthed instruments, and I quite like them that way. It's not legacy, it's hardcoded, old style. - kevinz000
|
||||
/datum/instrument/hardcoded
|
||||
abstract_type = /datum/instrument/hardcoded
|
||||
category = "Non-Synthesized"
|
||||
instrument_flags = INSTRUMENT_LEGACY
|
||||
volume_multiplier = 1 //not as loud as synth'd
|
||||
|
||||
/datum/instrument/hardcoded/accordian
|
||||
name = "Accordian"
|
||||
id = "accordian"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "accordian"
|
||||
|
||||
/datum/instrument/hardcoded/bikehorn
|
||||
name = "Bike Horn"
|
||||
id = "bikehorn"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "bikehorn"
|
||||
|
||||
/datum/instrument/hardcoded/eguitar
|
||||
name = "Electric Guitar"
|
||||
id = "eguitar"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "eguitar"
|
||||
|
||||
/datum/instrument/hardcoded/glockenspiel
|
||||
name = "Glockenspiel"
|
||||
id = "glockenspiel"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "glockenspiel"
|
||||
|
||||
/datum/instrument/hardcoded/guitar
|
||||
name = "Guitar"
|
||||
id = "guitar"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "guitar"
|
||||
|
||||
/datum/instrument/hardcoded/harmonica
|
||||
name = "Harmonica"
|
||||
id = "harmonica"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "harmonica"
|
||||
|
||||
/datum/instrument/hardcoded/piano
|
||||
name = "Piano"
|
||||
id = "piano"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "piano"
|
||||
|
||||
/datum/instrument/hardcoded/recorder
|
||||
name = "Recorder"
|
||||
id = "recorder"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "recorder"
|
||||
|
||||
/datum/instrument/hardcoded/saxophone
|
||||
name = "Saxophone"
|
||||
id = "saxophone"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "saxophone"
|
||||
|
||||
/datum/instrument/hardcoded/trombone
|
||||
name = "Trombone"
|
||||
id = "trombone"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "trombone"
|
||||
|
||||
/datum/instrument/hardcoded/violin
|
||||
name = "Violin"
|
||||
id = "violin"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "violin"
|
||||
|
||||
/datum/instrument/hardcoded/xylophone
|
||||
name = "Xylophone"
|
||||
id = "xylophone"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "xylophone"
|
||||
|
||||
/datum/instrument/hardcoded/banjo
|
||||
name = "Banjo"
|
||||
id = "banjo"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "banjo"
|
||||
@@ -0,0 +1,43 @@
|
||||
/datum/instrument/organ
|
||||
name = "Generic organ"
|
||||
category = "Organ"
|
||||
abstract_type = /datum/instrument/organ
|
||||
|
||||
/datum/instrument/organ/crisis_church
|
||||
name = "Crisis Church Organ"
|
||||
id = "crichugan"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_hammond
|
||||
name = "Crisis Hammond Organ"
|
||||
id = "crihamgan"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_accordian
|
||||
name = "Crisis Accordian"
|
||||
id = "crack"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_harmonica
|
||||
name = "Crisis Harmonica"
|
||||
id = "crharmony"
|
||||
real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_tango_accordian
|
||||
name = "Crisis Tango Accordian"
|
||||
id = "crtango"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')
|
||||
@@ -0,0 +1,56 @@
|
||||
/datum/instrument/piano
|
||||
name = "Generic piano"
|
||||
category = "Piano"
|
||||
abstract_type = /datum/instrument/piano
|
||||
|
||||
/datum/instrument/piano/fluid_piano
|
||||
name = "FluidR3 Grand Piano"
|
||||
id = "r3grand"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg')
|
||||
|
||||
/datum/instrument/piano/fluid_harpsichord
|
||||
name = "FluidR3 Harpsichord"
|
||||
id = "r3harpsi"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg')
|
||||
|
||||
/datum/instrument/piano/crisis_harpsichord
|
||||
name = "Crisis Harpsichord"
|
||||
id = "crharpsi"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg')
|
||||
|
||||
/datum/instrument/piano/crisis_grandpiano_uni
|
||||
name = "Crisis Grand Piano One"
|
||||
id = "crgrand1"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg')
|
||||
|
||||
/datum/instrument/piano/crisis_brightpiano_uni
|
||||
name = "Crisis Bright Piano One"
|
||||
id = "crbright1"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg')
|
||||
@@ -0,0 +1,19 @@
|
||||
/datum/instrument/tones
|
||||
name = "Ideal tone"
|
||||
category = "Tones"
|
||||
abstract_type = /datum/instrument/tones
|
||||
|
||||
/datum/instrument/tones/square_wave
|
||||
name = "Ideal square wave"
|
||||
id = "square"
|
||||
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Square.ogg')
|
||||
|
||||
/datum/instrument/tones/sine_wave
|
||||
name = "Ideal sine wave"
|
||||
id = "sine"
|
||||
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sine.ogg')
|
||||
|
||||
/datum/instrument/tones/saw_wave
|
||||
name = "Ideal sawtooth wave"
|
||||
id = "saw"
|
||||
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sawtooth.ogg')
|
||||
@@ -0,0 +1,298 @@
|
||||
//copy pasta of the space piano, don't hurt me -Pete
|
||||
/obj/item/instrument
|
||||
name = "generic instrument"
|
||||
force = 10
|
||||
max_integrity = 100
|
||||
resistance_flags = FLAMMABLE
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
|
||||
var/datum/song/handheld/song
|
||||
var/list/allowed_instrument_ids
|
||||
var/tune_time_left = 0
|
||||
|
||||
/obj/item/instrument/Initialize(mapload)
|
||||
. = ..()
|
||||
song = new(src, allowed_instrument_ids)
|
||||
allowed_instrument_ids = null //We don't need this clogging memory after it's used.
|
||||
|
||||
/obj/item/instrument/Destroy()
|
||||
QDEL_NULL(song)
|
||||
if(tune_time_left)
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
return ..()
|
||||
|
||||
/obj/item/instrument/proc/should_stop_playing(mob/user)
|
||||
return !user.CanReach(src) || !user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE)
|
||||
|
||||
/obj/item/instrument/process(wait)
|
||||
if(is_tuned())
|
||||
if (song.playing)
|
||||
for (var/mob/living/M in song.hearing_mobs)
|
||||
M.dizziness = max(0,M.dizziness-2)
|
||||
M.jitteriness = max(0,M.jitteriness-2)
|
||||
M.confused = max(M.confused-1)
|
||||
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "goodmusic", /datum/mood_event/goodmusic)
|
||||
tune_time_left -= wait
|
||||
else
|
||||
tune_time_left = 0
|
||||
if (song.playing)
|
||||
loc.visible_message("<span class='warning'>[src] starts sounding a little off...</span>")
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
|
||||
/obj/item/instrument/suicide_act(mob/user)
|
||||
user.visible_message("<span class='suicide'>[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
return (BRUTELOSS)
|
||||
|
||||
/obj/item/instrument/attack_self(mob/user)
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return TRUE
|
||||
interact(user)
|
||||
|
||||
/obj/item/instrument/attackby(obj/item/W, mob/user, params)
|
||||
if(istype(W, /obj/item/musicaltuner))
|
||||
var/mob/living/carbon/human/H = user
|
||||
if (HAS_TRAIT(H, TRAIT_MUSICIAN))
|
||||
if (!is_tuned())
|
||||
H.visible_message("[H] tunes the [src] to perfection!", "<span class='notice'>You tune the [src] to perfection!</span>")
|
||||
tune_time_left = 600 SECONDS
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
else
|
||||
to_chat(H, "<span class='notice'>[src] is already well tuned!</span>")
|
||||
else
|
||||
to_chat(H, "<span class='warning'>You have no idea how to use this.</span>")
|
||||
|
||||
/obj/item/instrument/proc/is_tuned()
|
||||
return tune_time_left > 0
|
||||
|
||||
/obj/item/instrument/interact(mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/item/instrument/ui_interact(mob/living/user)
|
||||
if(!isliving(user) || user.stat || user.restrained())
|
||||
return
|
||||
|
||||
user.set_machine(src)
|
||||
song.ui_interact(user)
|
||||
|
||||
/obj/item/instrument/violin
|
||||
name = "space violin"
|
||||
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon_state = "violin"
|
||||
item_state = "violin"
|
||||
hitsound = "swing_hit"
|
||||
allowed_instrument_ids = "violin"
|
||||
|
||||
/obj/item/instrument/violin/golden
|
||||
name = "golden violin"
|
||||
desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon_state = "golden_violin"
|
||||
item_state = "golden_violin"
|
||||
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
|
||||
|
||||
/obj/item/instrument/piano_synth
|
||||
name = "synthesizer"
|
||||
desc = "An advanced electronic synthesizer that can be used as various instruments."
|
||||
icon_state = "synth"
|
||||
item_state = "synth"
|
||||
allowed_instrument_ids = "piano"
|
||||
|
||||
/obj/item/instrument/piano_synth/Initialize()
|
||||
. = ..()
|
||||
song.allowed_instrument_ids = get_allowed_instrument_ids()
|
||||
|
||||
/obj/item/instrument/guitar
|
||||
name = "guitar"
|
||||
desc = "It's made of wood and has bronze strings."
|
||||
icon_state = "guitar"
|
||||
item_state = "guitar"
|
||||
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.ogg'
|
||||
allowed_instrument_ids = "guitar"
|
||||
|
||||
/obj/item/instrument/eguitar
|
||||
name = "electric guitar"
|
||||
desc = "Makes all your shredding needs possible."
|
||||
icon_state = "eguitar"
|
||||
item_state = "eguitar"
|
||||
force = 12
|
||||
attack_verb = list("played metal on", "shredded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.ogg'
|
||||
allowed_instrument_ids = "eguitar"
|
||||
|
||||
/obj/item/instrument/glockenspiel
|
||||
name = "glockenspiel"
|
||||
desc = "Smooth metal bars perfect for any marching band."
|
||||
icon_state = "glockenspiel"
|
||||
item_state = "glockenspiel"
|
||||
allowed_instrument_ids = "glockenspiel"
|
||||
|
||||
/obj/item/instrument/accordion
|
||||
name = "accordion"
|
||||
desc = "Pun-Pun not included."
|
||||
icon_state = "accordion"
|
||||
item_state = "accordion"
|
||||
allowed_instrument_ids = "accordion"
|
||||
|
||||
/obj/item/instrument/trumpet
|
||||
name = "trumpet"
|
||||
desc = "To announce the arrival of the king!"
|
||||
icon_state = "trumpet"
|
||||
item_state = "trombone"
|
||||
allowed_instrument_ids = "trombone"
|
||||
|
||||
/obj/item/instrument/trumpet/spectral
|
||||
name = "spectral trumpet"
|
||||
desc = "Things are about to get spooky!"
|
||||
icon_state = "trumpet"
|
||||
item_state = "trombone"
|
||||
force = 0
|
||||
attack_verb = list("played","jazzed","trumpeted","mourned","dooted","spooked")
|
||||
|
||||
/obj/item/instrument/trumpet/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
|
||||
/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound (loc, 'sound/instruments/trombone/En4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/saxophone
|
||||
name = "saxophone"
|
||||
desc = "This soothing sound will be sure to leave your audience in tears."
|
||||
icon_state = "saxophone"
|
||||
item_state = "saxophone"
|
||||
allowed_instrument_ids = "saxophone"
|
||||
|
||||
/obj/item/instrument/saxophone/spectral
|
||||
name = "spectral saxophone"
|
||||
desc = "This spooky sound will be sure to leave mortals in bones."
|
||||
icon_state = "saxophone"
|
||||
item_state = "saxophone"
|
||||
force = 0
|
||||
attack_verb = list("played","jazzed","saxxed","mourned","dooted","spooked")
|
||||
|
||||
/obj/item/instrument/saxophone/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
|
||||
/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound(loc, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/trombone
|
||||
name = "trombone"
|
||||
desc = "How can any pool table ever hope to compete?"
|
||||
icon_state = "trombone"
|
||||
item_state = "trombone"
|
||||
allowed_instrument_ids = "trombone"
|
||||
|
||||
/obj/item/instrument/trombone/spectral
|
||||
name = "spectral trombone"
|
||||
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
|
||||
icon_state = "trombone"
|
||||
item_state = "trombone"
|
||||
force = 0
|
||||
attack_verb = list("played","jazzed","tromboned","mourned","dooted","spooked")
|
||||
|
||||
/obj/item/instrument/trombone/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
|
||||
/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound (loc, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/recorder
|
||||
name = "recorder"
|
||||
desc = "Just like in school, playing ability and all."
|
||||
force = 5
|
||||
icon_state = "recorder"
|
||||
item_state = "recorder"
|
||||
allowed_instrument_ids = "recorder"
|
||||
|
||||
/obj/item/instrument/harmonica
|
||||
name = "harmonica"
|
||||
desc = "For when you get a bad case of the space blues."
|
||||
icon_state = "harmonica"
|
||||
item_state = "harmonica"
|
||||
allowed_instrument_ids = "harmonica"
|
||||
slot_flags = ITEM_SLOT_MASK
|
||||
force = 5
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
actions_types = list(/datum/action/item_action/instrument)
|
||||
|
||||
/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
|
||||
if(song.playing && ismob(loc))
|
||||
to_chat(loc, "<span class='warning'>You stop playing the harmonica to talk...</span>")
|
||||
song.playing = FALSE
|
||||
|
||||
/obj/item/instrument/harmonica/equipped(mob/M, slot)
|
||||
. = ..()
|
||||
RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
|
||||
|
||||
/obj/item/instrument/harmonica/dropped(mob/M)
|
||||
. = ..()
|
||||
UnregisterSignal(M, COMSIG_MOB_SAY)
|
||||
|
||||
/obj/item/instrument/bikehorn
|
||||
name = "gilded bike horn"
|
||||
desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes."
|
||||
icon_state = "bike_horn"
|
||||
item_state = "bike_horn"
|
||||
lefthand_file = 'icons/mob/inhands/equipment/horns_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/equipment/horns_righthand.dmi'
|
||||
attack_verb = list("beautifully honks")
|
||||
allowed_instrument_ids = "bikehorn"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
force = 0
|
||||
throw_speed = 3
|
||||
throw_range = 15
|
||||
hitsound = 'sound/items/bikehorn.ogg'
|
||||
|
||||
/obj/item/instrument/banjo
|
||||
name = "banjo"
|
||||
desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
|
||||
icon_state = "banjo"
|
||||
item_state = "banjo"
|
||||
attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered")
|
||||
hitsound = 'sound/weapons/banjoslap.ogg'
|
||||
allowed_instrument_ids = "banjo"
|
||||
|
||||
/obj/item/musicaltuner
|
||||
name = "musical tuner"
|
||||
desc = "A device for tuning musical instruments both manual and electronic alike."
|
||||
icon = 'icons/obj/device.dmi'
|
||||
icon_state = "musicaltuner"
|
||||
slot_flags = ITEM_SLOT_BELT
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
item_state = "electronic"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
|
||||
/obj/item/choice_beacon/music
|
||||
name = "instrument delivery beacon"
|
||||
desc = "Summon your tool of art."
|
||||
icon_state = "gangtool-red"
|
||||
|
||||
/obj/item/choice_beacon/music/generate_display_names()
|
||||
var/static/list/instruments
|
||||
if(!instruments)
|
||||
instruments = list()
|
||||
var/list/templist = list(/obj/item/instrument/violin,
|
||||
/obj/item/instrument/piano_synth,
|
||||
/obj/item/instrument/guitar,
|
||||
/obj/item/instrument/eguitar,
|
||||
/obj/item/instrument/glockenspiel,
|
||||
/obj/item/instrument/accordion,
|
||||
/obj/item/instrument/trumpet,
|
||||
/obj/item/instrument/saxophone,
|
||||
/obj/item/instrument/trombone,
|
||||
/obj/item/instrument/recorder,
|
||||
/obj/item/instrument/harmonica
|
||||
)
|
||||
for(var/V in templist)
|
||||
var/atom/A = V
|
||||
instruments[initial(A.name)] = A
|
||||
return instruments
|
||||
@@ -0,0 +1,52 @@
|
||||
/obj/structure/musician
|
||||
name = "Not A Piano"
|
||||
desc = "Something broke, contact coderbus."
|
||||
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_DEXTERITY
|
||||
var/can_play_unanchored = FALSE
|
||||
var/list/allowed_instrument_ids
|
||||
var/datum/song/song
|
||||
|
||||
/obj/structure/musician/Initialize(mapload)
|
||||
. = ..()
|
||||
song = new(src, allowed_instrument_ids)
|
||||
allowed_instrument_ids = null
|
||||
|
||||
/obj/structure/musician/Destroy()
|
||||
QDEL_NULL(song)
|
||||
return ..()
|
||||
|
||||
/obj/structure/musician/proc/should_stop_playing(mob/user)
|
||||
if(!(anchored || can_play_unanchored))
|
||||
return TRUE
|
||||
if(!user)
|
||||
return FALSE
|
||||
return !user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE) //can play with TK and while resting because fun.
|
||||
|
||||
/obj/structure/musician/ui_interact(mob/user)
|
||||
. = ..()
|
||||
song.ui_interact(user)
|
||||
|
||||
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
|
||||
default_unfasten_wrench(user, I, 40)
|
||||
return TRUE
|
||||
|
||||
/obj/structure/musician/piano
|
||||
name = "space minimoog"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "minimoog"
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
|
||||
/obj/structure/musician/piano/unanchored
|
||||
anchored = FALSE
|
||||
|
||||
/obj/structure/musician/piano/Initialize(mapload)
|
||||
. = ..()
|
||||
if(prob(50) && icon_state == initial(icon_state))
|
||||
name = "space minimoog"
|
||||
desc = "This is a minimoog, like a space piano, but more spacey!"
|
||||
icon_state = "minimoog"
|
||||
else
|
||||
name = "space piano"
|
||||
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
|
||||
icon_state = "piano"
|
||||
@@ -0,0 +1,301 @@
|
||||
#define MUSICIAN_HEARCHECK_MINDELAY 4
|
||||
#define MUSIC_MAXLINES 1000
|
||||
#define MUSIC_MAXLINECHARS 300
|
||||
|
||||
/datum/song
|
||||
/// Name of the song
|
||||
var/name = "Untitled"
|
||||
|
||||
/// The atom we're attached to/playing from
|
||||
var/atom/parent
|
||||
|
||||
/// Our song lines
|
||||
var/list/lines
|
||||
|
||||
/// delay between notes in deciseconds
|
||||
var/tempo = 5
|
||||
|
||||
/// Are we currently playing?
|
||||
var/playing = FALSE
|
||||
|
||||
/// Are we currently editing?
|
||||
var/editing = TRUE
|
||||
/// Is the help screen open?
|
||||
var/help = FALSE
|
||||
|
||||
/// Repeats left
|
||||
var/repeat = 0
|
||||
/// Maximum times we can repeat
|
||||
var/max_repeats = 10
|
||||
|
||||
/// Our volume
|
||||
var/volume = 75
|
||||
/// Max volume
|
||||
var/max_volume = 75
|
||||
/// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs.
|
||||
var/min_volume = 1
|
||||
|
||||
/// What instruments our built in picker can use. The picker won't show unless this is longer than one.
|
||||
var/list/allowed_instrument_ids = list("r3grand")
|
||||
|
||||
//////////// Cached instrument variables /////////////
|
||||
/// Instrument we are currently using
|
||||
var/datum/instrument/using_instrument
|
||||
/// Cached legacy ext for legacy instruments
|
||||
var/cached_legacy_ext
|
||||
/// Cached legacy dir for legacy instruments
|
||||
var/cached_legacy_dir
|
||||
/// Cached list of samples, referenced directly from the instrument for synthesized instruments
|
||||
var/list/cached_samples
|
||||
/// Are we operating in legacy mode (so if the instrument is a legacy instrument)
|
||||
var/legacy = FALSE
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/////////////////// Playing variables ////////////////
|
||||
/**
|
||||
* Only used in synthesized playback - The chords we compiled. Non assoc list of lists:
|
||||
* list(list(key1, key2, key3..., tempo_divisor), list(key1, key2..., tempo_divisor), ...)
|
||||
* tempo_divisor always exists
|
||||
* if key1 (and so if there's no keys) doesn't exist it's a rest
|
||||
* Compilation happens when we start playing and is cleared after we finish playing.
|
||||
*/
|
||||
var/list/compiled_chords
|
||||
/// Channel as text = current volume
|
||||
var/list/channels_playing = list()
|
||||
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
|
||||
var/list/channels_idle = list()
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/// Last world.time we checked for who can hear us
|
||||
var/last_hearcheck = 0
|
||||
/// The list of mobs that can hear us
|
||||
var/list/hearing_mobs
|
||||
/// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
|
||||
var/debug_mode = FALSE
|
||||
/// Last time we processed decay
|
||||
var/last_process_decay
|
||||
/// Max sound channels to occupy
|
||||
var/max_sound_channels = CHANNELS_PER_INSTRUMENT
|
||||
/// Current channels, so we can save a length() call.
|
||||
var/using_sound_channels = 0
|
||||
/// Last channel to play. text.
|
||||
var/last_channel_played
|
||||
/// Should we not decay our last played note?
|
||||
var/full_sustain_held_note = TRUE
|
||||
|
||||
/////////////////////// DO NOT TOUCH THESE ///////////////////
|
||||
var/octave_min = INSTRUMENT_MIN_OCTAVE
|
||||
var/octave_max = INSTRUMENT_MAX_OCTAVE
|
||||
var/key_min = INSTRUMENT_MIN_KEY
|
||||
var/key_max = INSTRUMENT_MAX_KEY
|
||||
var/static/list/note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7)
|
||||
var/static/list/accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
///////////// !!FUN!! - Only works in synthesized mode! /////////////////
|
||||
/// Note numbers to shift.
|
||||
var/note_shift = 0
|
||||
var/note_shift_min = -100
|
||||
var/note_shift_max = 100
|
||||
var/can_noteshift = TRUE
|
||||
/// The kind of sustain we're using
|
||||
var/sustain_mode = SUSTAIN_LINEAR
|
||||
/// When a note is considered dead if it is below this in volume
|
||||
var/sustain_dropoff_volume = 0
|
||||
/// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF
|
||||
var/sustain_linear_duration = 5
|
||||
/// Exponential sustain dropoff rate per decisecond
|
||||
var/sustain_exponential_dropoff = 1.4
|
||||
////////// DO NOT DIRECTLY SET THESE!
|
||||
/// Do not directly set, use update_sustain()
|
||||
var/cached_linear_dropoff = 10
|
||||
/// Do not directly set, use update_sustain()
|
||||
var/cached_exponential_dropoff = 1.045
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/datum/song/New(atom/parent, list/instrument_ids)
|
||||
SSinstruments.on_song_new(src)
|
||||
lines = list()
|
||||
tempo = sanitize_tempo(tempo)
|
||||
src.parent = parent
|
||||
if(instrument_ids)
|
||||
allowed_instrument_ids = islist(instrument_ids)? instrument_ids : list(instrument_ids)
|
||||
if(length(allowed_instrument_ids))
|
||||
set_instrument(allowed_instrument_ids[1])
|
||||
hearing_mobs = list()
|
||||
volume = clamp(volume, min_volume, max_volume)
|
||||
update_sustain()
|
||||
|
||||
/datum/song/Destroy()
|
||||
stop_playing()
|
||||
SSinstruments.on_song_del(src)
|
||||
lines = null
|
||||
using_instrument = null
|
||||
allowed_instrument_ids = null
|
||||
parent = null
|
||||
return ..()
|
||||
|
||||
/datum/song/proc/do_hearcheck()
|
||||
last_hearcheck = world.time
|
||||
var/list/old = hearing_mobs.Copy()
|
||||
hearing_mobs.len = 0
|
||||
var/turf/source = get_turf(parent)
|
||||
for(var/mob/M in get_hearers_in_view(15, source))
|
||||
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
hearing_mobs[M] = get_dist(M, source)
|
||||
var/list/exited = old - hearing_mobs
|
||||
for(var/i in exited)
|
||||
terminate_sound_mob(i)
|
||||
|
||||
/// I can either be a datum, id, or path (if the instrument has no id).
|
||||
/datum/song/proc/set_instrument(datum/instrument/I)
|
||||
if(using_instrument)
|
||||
using_instrument.songs_using -= src
|
||||
using_instrument = null
|
||||
cached_samples = null
|
||||
cached_legacy_ext = null
|
||||
cached_legacy_dir = null
|
||||
legacy = null
|
||||
if(istext(I) || ispath(I))
|
||||
I = SSinstruments.instrument_data[I]
|
||||
if(istype(I))
|
||||
using_instrument = I
|
||||
I.songs_using += src
|
||||
var/instrument_legacy = CHECK_BITFIELD(I.instrument_flags, INSTRUMENT_LEGACY)
|
||||
if(instrument_legacy)
|
||||
cached_legacy_ext = I.legacy_instrument_ext
|
||||
cached_legacy_dir = I.legacy_instrument_path
|
||||
legacy = TRUE
|
||||
else
|
||||
cached_samples = I.samples
|
||||
legacy = FALSE
|
||||
|
||||
/// THIS IS A BLOCKING CALL.
|
||||
/datum/song/proc/start_playing(mob/user)
|
||||
if(playing)
|
||||
return
|
||||
if(!using_instrument?.ready())
|
||||
to_chat(user, "<span class='warning'>An error has occured with [src]. Please reset the instrument.</span>")
|
||||
return
|
||||
playing = TRUE
|
||||
updateDialog()
|
||||
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
|
||||
//wrap the rest of the stuff to ensure stop_playing() is called.
|
||||
last_process_decay = world.time
|
||||
START_PROCESSING(SSinstruments, src)
|
||||
. = do_play_lines(user)
|
||||
stop_playing()
|
||||
|
||||
/datum/song/proc/stop_playing()
|
||||
if(!playing)
|
||||
return
|
||||
playing = FALSE
|
||||
if(!debug_mode)
|
||||
compiled_chords = null
|
||||
STOP_PROCESSING(SSinstruments, src)
|
||||
terminate_all_sounds(TRUE)
|
||||
hearing_mobs.len = 0
|
||||
updateDialog()
|
||||
|
||||
/// THIS IS A BLOCKING CALL.
|
||||
/datum/song/proc/do_play_lines(user)
|
||||
if(!playing)
|
||||
return
|
||||
do_hearcheck()
|
||||
if(legacy)
|
||||
do_play_lines_legacy(user)
|
||||
else
|
||||
do_play_lines_synthesized(user)
|
||||
|
||||
/datum/song/proc/should_stop_playing(mob/user)
|
||||
return QDELETED(parent) || !using_instrument || !playing
|
||||
|
||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||
new_tempo = abs(new_tempo)
|
||||
return CLAMP(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
|
||||
|
||||
/datum/song/proc/get_bpm()
|
||||
return 600 / tempo
|
||||
|
||||
/datum/song/proc/set_bpm(bpm)
|
||||
tempo = sanitize_tempo(600 / bpm)
|
||||
|
||||
/// Updates the window for our user. Override in subtypes.
|
||||
/datum/song/proc/updateDialog(mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/datum/song/process(wait)
|
||||
if(!playing)
|
||||
return PROCESS_KILL
|
||||
var/delay = world.time - last_process_decay
|
||||
process_decay(delay)
|
||||
last_process_decay = world.time
|
||||
|
||||
/datum/song/proc/update_sustain()
|
||||
// Exponential is easy
|
||||
cached_exponential_dropoff = sustain_exponential_dropoff
|
||||
// Linear, not so much, since it's a target duration from 100 volume rather than an exponential rate.
|
||||
var/target_duration = sustain_linear_duration
|
||||
var/volume_diff = max(0, volume - sustain_dropoff_volume)
|
||||
var/volume_decrease_per_decisecond = volume_diff / target_duration
|
||||
cached_linear_dropoff = volume_decrease_per_decisecond
|
||||
|
||||
/datum/song/proc/set_volume(volume)
|
||||
src.volume = CLAMP(volume, max(0, min_volume), min(100, max_volume))
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/datum/song/proc/set_dropoff_volume(volume)
|
||||
sustain_dropoff_volume = CLAMP(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/datum/song/proc/set_exponential_drop_rate(drop)
|
||||
sustain_exponential_dropoff = CLAMP(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/datum/song/proc/set_linear_falloff_duration(duration)
|
||||
sustain_linear_duration = CLAMP(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/datum/song/vv_edit_var(var_name, var_value)
|
||||
. = ..()
|
||||
if(.)
|
||||
switch(var_name)
|
||||
if(NAMEOF(src, volume))
|
||||
set_volume(var_value)
|
||||
if(NAMEOF(src, sustain_dropoff_volume))
|
||||
set_dropoff_volume(var_value)
|
||||
if(NAMEOF(src, sustain_exponential_dropoff))
|
||||
set_exponential_drop_rate(var_value)
|
||||
if(NAMEOF(src, sustain_linear_duration))
|
||||
set_linear_falloff_duration(var_value)
|
||||
|
||||
// subtype for handheld instruments, like violin
|
||||
/datum/song/handheld
|
||||
|
||||
/datum/song/handheld/updateDialog(mob/user)
|
||||
parent.ui_interact(user || usr)
|
||||
|
||||
/datum/song/handheld/should_stop_playing(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
var/obj/item/instrument/I = parent
|
||||
return I.should_stop_playing(user)
|
||||
|
||||
// subtype for stationary structures, like pianos
|
||||
/datum/song/stationary
|
||||
|
||||
/datum/song/stationary/updateDialog(mob/user)
|
||||
parent.ui_interact(user || usr)
|
||||
|
||||
/datum/song/stationary/should_stop_playing(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
var/obj/structure/musician/M = parent
|
||||
return M.should_stop_playing(user)
|
||||
@@ -0,0 +1,246 @@
|
||||
/datum/song/proc/instrument_status_ui()
|
||||
. = list()
|
||||
. += "<div class='statusDisplay'>"
|
||||
. += "<b><a href='?src=[REF(src)];switchinstrument=1'>Current instrument</a>:</b> "
|
||||
if(!using_instrument)
|
||||
. += "<span class='danger'>No instrument loaded!</span><br>"
|
||||
else
|
||||
. += "[using_instrument.name]<br>"
|
||||
. += "Playback Settings:<br>"
|
||||
if(can_noteshift)
|
||||
. += "<a href='?src=[REF(src)];setnoteshift=1'>Note Shift/Note Transpose</a>: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves<br>"
|
||||
var/smt
|
||||
var/modetext = ""
|
||||
switch(sustain_mode)
|
||||
if(SUSTAIN_LINEAR)
|
||||
smt = "Linear"
|
||||
modetext = "<a href='?src=[REF(src)];setlinearfalloff=1'>Linear Sustain Duration</a>: [sustain_linear_duration / 10] seconds<br>"
|
||||
if(SUSTAIN_EXPONENTIAL)
|
||||
smt = "Exponential"
|
||||
modetext = "<a href='?src=[REF(src)];setexpfalloff=1'>Exponential Falloff Factor</a>: [sustain_exponential_dropoff]% per decisecond<br>"
|
||||
. += "<a href='?src=[REF(src)];setsustainmode=1'>Sustain Mode</a>: [smt]<br>"
|
||||
. += modetext
|
||||
. += using_instrument?.ready()? "Status: <span class='good'>Ready</span><br>" : "Status: <span class='bad'>!Instrument Definition Error!</span><br>"
|
||||
. += "Instrument Type: [legacy? "Legacy" : "Synthesized"]<br>"
|
||||
. += "<a href='?src=[REF(src)];setvolume=1'>Volume</a>: [volume]<br>"
|
||||
. += "<a href='?src=[REF(src)];setdropoffvolume=1'>Volume Dropoff Threshold</a>: [sustain_dropoff_volume]<br>"
|
||||
. += "<a href='?src=[REF(src)];togglesustainhold=1'>Sustain indefinitely last held note</a>: [full_sustain_held_note? "Enabled" : "Disabled"].<br>"
|
||||
. += "</div>"
|
||||
|
||||
/datum/song/ui_interact(mob/user)
|
||||
var/list/dat = list()
|
||||
|
||||
dat += instrument_status_ui()
|
||||
|
||||
if(lines.len > 0)
|
||||
dat += "<H3>Playback</H3>"
|
||||
if(!playing)
|
||||
dat += "<A href='?src=[REF(src)];play=1'>Play</A> <SPAN CLASS='linkOn'>Stop</SPAN><BR><BR>"
|
||||
dat += "Repeat Song: "
|
||||
dat += repeat > 0 ? "<A href='?src=[REF(src)];repeat=-10'>-</A><A href='?src=[REF(src)];repeat=-1'>-</A>" : "<SPAN CLASS='linkOff'>-</SPAN><SPAN CLASS='linkOff'>-</SPAN>"
|
||||
dat += " [repeat] times "
|
||||
dat += repeat < max_repeats ? "<A href='?src=[REF(src)];repeat=1'>+</A><A href='?src=[REF(src)];repeat=10'>+</A>" : "<SPAN CLASS='linkOff'>+</SPAN><SPAN CLASS='linkOff'>+</SPAN>"
|
||||
dat += "<BR>"
|
||||
else
|
||||
dat += "<SPAN CLASS='linkOn'>Play</SPAN> <A href='?src=[REF(src)];stop=1'>Stop</A><BR>"
|
||||
dat += "Repeats left: <B>[repeat]</B><BR>"
|
||||
if(!editing)
|
||||
dat += "<BR><B><A href='?src=[REF(src)];edit=2'>Show Editor</A></B><BR>"
|
||||
else
|
||||
dat += "<H3>Editing</H3>"
|
||||
dat += "<B><A href='?src=[REF(src)];edit=1'>Hide Editor</A></B>"
|
||||
dat += " <A href='?src=[REF(src)];newsong=1'>Start a New Song</A>"
|
||||
dat += " <A href='?src=[REF(src)];import=1'>Import a Song</A><BR><BR>"
|
||||
var/bpm = round(600 / tempo)
|
||||
dat += "Tempo: <A href='?src=[REF(src)];tempo=[world.tick_lag]'>-</A> [bpm] BPM <A href='?src=[REF(src)];tempo=-[world.tick_lag]'>+</A><BR><BR>"
|
||||
var/linecount = 0
|
||||
for(var/line in lines)
|
||||
linecount += 1
|
||||
dat += "Line [linecount]: <A href='?src=[REF(src)];modifyline=[linecount]'>Edit</A> <A href='?src=[REF(src)];deleteline=[linecount]'>X</A> [line]<BR>"
|
||||
dat += "<A href='?src=[REF(src)];newline=1'>Add Line</A><BR><BR>"
|
||||
if(help)
|
||||
dat += "<B><A href='?src=[REF(src)];help=1'>Hide Help</A></B><BR>"
|
||||
dat += {"
|
||||
Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).<br>
|
||||
Every note in a chord will play together, with chord timed by the tempo.<br>
|
||||
<br>
|
||||
Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.<br>
|
||||
By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.<br>
|
||||
Example: <i>C,D,E,F,G,A,B</i> will play a C major scale.<br>
|
||||
After a note has an accidental placed, it will be remembered: <i>C,C4,C,C3</i> is <i>C3,C4,C4,C3</i><br>
|
||||
Chords can be played simply by seperating each note with a hyphon: <i>A-C#,Cn-E,E-G#,Gn-B</i><br>
|
||||
A pause may be denoted by an empty chord: <i>C,E,,C,G</i><br>
|
||||
To make a chord be a different time, end it with /x, where the chord length will be length<br>
|
||||
defined by tempo / x: <i>C,G/2,E/4</i><br>
|
||||
Combined, an example is: <i>E-E4/4,F#/2,G#/8,B/8,E3-E4/4</i>
|
||||
<br>
|
||||
Lines may be up to [MUSIC_MAXLINECHARS] characters.<br>
|
||||
A song may only contain up to [MUSIC_MAXLINES] lines.<br>
|
||||
"}
|
||||
else
|
||||
dat += "<B><A href='?src=[REF(src)];help=2'>Show Help</A></B><BR>"
|
||||
|
||||
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
|
||||
popup.set_content(dat.Join(""))
|
||||
popup.set_title_image(user.browse_rsc_icon(parent.icon, parent.icon_state))
|
||||
popup.open()
|
||||
|
||||
/datum/song/proc/ParseSong(text)
|
||||
set waitfor = FALSE
|
||||
//split into lines
|
||||
lines = splittext(text, "\n")
|
||||
if(lines.len)
|
||||
var/bpm_string = "BPM: "
|
||||
if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
|
||||
var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
|
||||
tempo = sanitize_tempo(600 / round(divisor, 1))
|
||||
lines.Cut(1, 2)
|
||||
else
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
if(lines.len > MUSIC_MAXLINES)
|
||||
to_chat(usr, "Too many lines!")
|
||||
lines.Cut(MUSIC_MAXLINES + 1)
|
||||
var/linenum = 1
|
||||
for(var/l in lines)
|
||||
if(length_char(l) > MUSIC_MAXLINECHARS)
|
||||
to_chat(usr, "Line [linenum] too long!")
|
||||
lines.Remove(l)
|
||||
else
|
||||
linenum++
|
||||
updateDialog(usr) // make sure updates when complete
|
||||
|
||||
/datum/song/Topic(href, href_list)
|
||||
if(!usr.canUseTopic(parent, TRUE, FALSE, FALSE, FALSE))
|
||||
usr << browse(null, "window=instrument")
|
||||
usr.unset_machine()
|
||||
return
|
||||
|
||||
parent.add_fingerprint(usr)
|
||||
|
||||
if(href_list["newsong"])
|
||||
lines = new()
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
name = ""
|
||||
|
||||
else if(href_list["import"])
|
||||
var/t = ""
|
||||
do
|
||||
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
|
||||
if(!in_range(parent, usr))
|
||||
return
|
||||
|
||||
if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
||||
var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
|
||||
if(cont == "no")
|
||||
break
|
||||
while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
||||
ParseSong(t)
|
||||
|
||||
else if(href_list["help"])
|
||||
help = text2num(href_list["help"]) - 1
|
||||
|
||||
else if(href_list["edit"])
|
||||
editing = text2num(href_list["edit"]) - 1
|
||||
|
||||
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
|
||||
if(playing)
|
||||
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
|
||||
repeat += round(text2num(href_list["repeat"]))
|
||||
if(repeat < 0)
|
||||
repeat = 0
|
||||
if(repeat > max_repeats)
|
||||
repeat = max_repeats
|
||||
|
||||
else if(href_list["tempo"])
|
||||
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
|
||||
|
||||
else if(href_list["play"])
|
||||
INVOKE_ASYNC(src, .proc/start_playing, usr)
|
||||
|
||||
else if(href_list["newline"])
|
||||
var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
|
||||
if(!newline || !in_range(parent, usr))
|
||||
return
|
||||
if(lines.len > MUSIC_MAXLINES)
|
||||
return
|
||||
if(length(newline) > MUSIC_MAXLINECHARS)
|
||||
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
|
||||
lines.Add(newline)
|
||||
|
||||
else if(href_list["deleteline"])
|
||||
var/num = round(text2num(href_list["deleteline"]))
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines.Cut(num, num+1)
|
||||
|
||||
else if(href_list["modifyline"])
|
||||
var/num = round(text2num(href_list["modifyline"]),1)
|
||||
var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
|
||||
if(!content || !in_range(parent, usr))
|
||||
return
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines[num] = content
|
||||
|
||||
else if(href_list["stop"])
|
||||
stop_playing()
|
||||
|
||||
else if(href_list["setlinearfalloff"])
|
||||
var/amount = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
|
||||
if(!isnull(amount))
|
||||
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
|
||||
|
||||
else if(href_list["setexpfalloff"])
|
||||
var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
|
||||
if(!isnull(amount))
|
||||
set_exponential_drop_rate(round(amount, 0.00001))
|
||||
|
||||
else if(href_list["setvolume"])
|
||||
var/amount = input(usr, "Set volume", "Volume") as null|num
|
||||
if(!isnull(amount))
|
||||
set_volume(round(amount, 1))
|
||||
|
||||
else if(href_list["setdropoffvolume"])
|
||||
var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
|
||||
if(!isnull(amount))
|
||||
set_dropoff_volume(round(amount, 0.01))
|
||||
|
||||
else if(href_list["switchinstrument"])
|
||||
if(!length(allowed_instrument_ids))
|
||||
return
|
||||
else if(length(allowed_instrument_ids) == 1)
|
||||
set_instrument(allowed_instrument_ids[1])
|
||||
return
|
||||
var/list/categories = list()
|
||||
for(var/i in allowed_instrument_ids)
|
||||
var/datum/instrument/I = SSinstruments.get_instrument(i)
|
||||
if(I)
|
||||
LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id)
|
||||
var/cat = input(usr, "Select Category", "Instrument Category") as null|anything in categories
|
||||
if(!cat)
|
||||
return
|
||||
var/list/instruments = categories[cat]
|
||||
var/choice = input(usr, "Select Instrument", "Instrument Selection") as null|anything in instruments
|
||||
if(!choice)
|
||||
return
|
||||
choice = instruments[choice] //get id
|
||||
if(choice)
|
||||
set_instrument(choice)
|
||||
|
||||
else if(href_list["setnoteshift"])
|
||||
var/amount = input(usr, "Set note shift", "Note Shift") as null|num
|
||||
if(!isnull(amount))
|
||||
note_shift = CLAMP(amount, note_shift_min, note_shift_max)
|
||||
|
||||
else if(href_list["setsustainmode"])
|
||||
var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in list("Linear", "Exponential")
|
||||
switch(choice)
|
||||
if("Linear")
|
||||
sustain_mode = SUSTAIN_LINEAR
|
||||
if("Exponential")
|
||||
sustain_mode = SUSTAIN_EXPONENTIAL
|
||||
|
||||
else if(href_list["togglesustainhold"])
|
||||
full_sustain_held_note = !full_sustain_held_note
|
||||
|
||||
updateDialog()
|
||||
@@ -0,0 +1,82 @@
|
||||
/// Playing legacy instruments - None of the "advanced" like sound reservations and decay are invoked.
|
||||
/datum/song/proc/do_play_lines_legacy(mob/user)
|
||||
while(repeat >= 0)
|
||||
var/cur_oct[7]
|
||||
var/cur_acc[7]
|
||||
for(var/i = 1 to 7)
|
||||
cur_oct[i] = 3
|
||||
cur_acc[i] = "n"
|
||||
|
||||
for(var/line in lines)
|
||||
for(var/beat in splittext(lowertext(line), ","))
|
||||
if(should_stop_playing(user))
|
||||
return
|
||||
var/list/notes = splittext(beat, "/")
|
||||
if(length(notes)) //because some jack-butts are going to do ,,,, to symbolize 3 rests instead of something reasonable like ,/1.
|
||||
for(var/note in splittext(notes[1], "-"))
|
||||
if(length(note) == 0)
|
||||
continue
|
||||
var/cur_note = text2ascii(note) - 96
|
||||
if(cur_note < 1 || cur_note > 7)
|
||||
continue
|
||||
for(var/i=2 to length(note))
|
||||
var/ni = copytext(note,i,i+1)
|
||||
if(!text2num(ni))
|
||||
if(ni == "#" || ni == "b" || ni == "n")
|
||||
cur_acc[cur_note] = ni
|
||||
else if(ni == "s")
|
||||
cur_acc[cur_note] = "#" // so shift is never required
|
||||
else
|
||||
cur_oct[cur_note] = text2num(ni)
|
||||
playnote_legacy(cur_note, cur_acc[cur_note], cur_oct[cur_note])
|
||||
if(notes.len >= 2 && text2num(notes[2]))
|
||||
sleep(sanitize_tempo(tempo / text2num(notes[2])))
|
||||
else
|
||||
sleep(tempo)
|
||||
if(should_stop_playing(user))
|
||||
return
|
||||
repeat--
|
||||
updateDialog()
|
||||
repeat = 0
|
||||
|
||||
// note is a number from 1-7 for A-G
|
||||
// acc is either "b", "n", or "#"
|
||||
// oct is 1-8 (or 9 for C)
|
||||
/datum/song/proc/playnote_legacy(note, acc as text, oct)
|
||||
// handle accidental -> B<>C of E<>F
|
||||
if(acc == "b" && (note == 3 || note == 6)) // C or F
|
||||
if(note == 3)
|
||||
oct--
|
||||
note--
|
||||
acc = "n"
|
||||
else if(acc == "#" && (note == 2 || note == 5)) // B or E
|
||||
if(note == 2)
|
||||
oct++
|
||||
note++
|
||||
acc = "n"
|
||||
else if(acc == "#" && (note == 7)) //G#
|
||||
note = 1
|
||||
acc = "b"
|
||||
else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
|
||||
acc = "b"
|
||||
note++
|
||||
|
||||
// check octave, C is allowed to go to 9
|
||||
if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
|
||||
return
|
||||
|
||||
// now generate name
|
||||
var/soundfile = "sound/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
|
||||
soundfile = file(soundfile)
|
||||
// make sure the note exists
|
||||
if(!fexists(soundfile))
|
||||
return
|
||||
// and play
|
||||
var/turf/source = get_turf(parent)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
do_hearcheck()
|
||||
var/sound/music_played = sound(soundfile)
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, falloff = 5, S = music_played)
|
||||
// Could do environment and echo later but not for now
|
||||
@@ -0,0 +1,135 @@
|
||||
/datum/song/proc/do_play_lines_synthesized(mob/user)
|
||||
compile_lines()
|
||||
while(repeat >= 0)
|
||||
if(should_stop_playing(user))
|
||||
return
|
||||
var/warned = FALSE
|
||||
for(var/_chord in compiled_chords)
|
||||
if(should_stop_playing(user))
|
||||
return
|
||||
var/list/chord = _chord
|
||||
var/tempodiv = chord[chord.len]
|
||||
for(var/i in 1 to chord.len - 1)
|
||||
var/key = chord[i]
|
||||
if(!playkey_synth(key))
|
||||
if(!warned)
|
||||
warned = TRUE
|
||||
to_chat(user, "<span class='boldwarning'>Your instrument has ran out of channels. You might be playing your song too fast or be setting sustain to too high of a value. This warning will be suppressed for the rest of this cycle.</span>")
|
||||
sleep(sanitize_tempo(tempo / (tempodiv || 1)))
|
||||
repeat--
|
||||
updateDialog()
|
||||
repeat = 0
|
||||
|
||||
/// C-Db2-A-A4/2,A-B#4-C/3,/4,A,A-B-C as an example
|
||||
/datum/song/proc/compile_lines()
|
||||
if(!length(src.lines))
|
||||
return
|
||||
var/list/lines = src.lines //cache for hyepr speed!
|
||||
compiled_chords = list()
|
||||
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
|
||||
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
|
||||
for(var/line in lines)
|
||||
var/list/chords = splittext(lowertext(line), ",")
|
||||
for(var/chord in chords)
|
||||
var/list/compiled_chord = list()
|
||||
var/tempodiv = 1
|
||||
var/list/notes_tempodiv = splittext(chord, "/")
|
||||
var/len = length(notes_tempodiv)
|
||||
if(len >= 2)
|
||||
tempodiv = text2num(notes_tempodiv[2])
|
||||
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
|
||||
var/list/notes = splittext(notes_tempodiv[1], "-")
|
||||
for(var/note in notes)
|
||||
if(length(note) == 0)
|
||||
continue
|
||||
// 1-7, A-G
|
||||
var/key = text2ascii(note) - 96
|
||||
if((key < 1) || (key > 7))
|
||||
continue
|
||||
for(var/i in 2 to length(note))
|
||||
var/oct_acc = copytext(note, i, i + 1)
|
||||
var/num = text2num(oct_acc)
|
||||
if(!num) //it's an accidental
|
||||
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
|
||||
else //octave
|
||||
octaves[key] = CLAMP(num, octave_min, octave_max)
|
||||
compiled_chord += CLAMP((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
|
||||
compiled_chord += tempodiv //this goes last
|
||||
if(length(compiled_chord))
|
||||
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||
CHECK_TICK
|
||||
return compiled_chords
|
||||
|
||||
/datum/song/proc/playkey_synth(key)
|
||||
if(can_noteshift)
|
||||
key = clamp(key + note_shift, key_min, key_max)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
do_hearcheck()
|
||||
var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
|
||||
//Should probably add channel limiters here at some point but I don't care right now.
|
||||
var/channel = pop_channel()
|
||||
if(isnull(channel))
|
||||
return FALSE
|
||||
. = TRUE
|
||||
var/sound/copy = sound(K.sample)
|
||||
var/volume = src.volume * using_instrument.volume_multiplier
|
||||
copy.frequency = K.frequency
|
||||
copy.volume = volume
|
||||
var/channel_text = num2text(channel)
|
||||
channels_playing[channel_text] = volume
|
||||
last_channel_played = channel_text
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
|
||||
// Could do environment and echo later but not for now
|
||||
|
||||
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
|
||||
for(var/i in hearing_mobs)
|
||||
terminate_sound_mob(i)
|
||||
if(clear_channels)
|
||||
channels_playing.len = 0
|
||||
channels_idle.len = 0
|
||||
SSinstruments.current_instrument_channels -= using_sound_channels
|
||||
using_sound_channels = 0
|
||||
SSsounds.free_datum_channels(src)
|
||||
|
||||
/datum/song/proc/terminate_sound_mob(mob/M)
|
||||
for(var/channel in channels_playing)
|
||||
M.stop_sound_channel(text2num(channel))
|
||||
|
||||
/datum/song/proc/pop_channel()
|
||||
if(length(channels_idle)) //just pop one off of here if we have one available
|
||||
. = text2num(channels_idle[1])
|
||||
channels_idle.Cut(1,2)
|
||||
return
|
||||
if(using_sound_channels >= max_sound_channels)
|
||||
return
|
||||
. = SSinstruments.reserve_instrument_channel(src)
|
||||
if(!isnull(.))
|
||||
using_sound_channels++
|
||||
|
||||
/datum/song/proc/process_decay(wait_ds)
|
||||
var/linear_dropoff = cached_linear_dropoff * wait_ds
|
||||
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
|
||||
for(var/channel in channels_playing)
|
||||
if(full_sustain_held_note && (channel == last_channel_played))
|
||||
continue
|
||||
var/current_volume = channels_playing[channel]
|
||||
switch(sustain_mode)
|
||||
if(SUSTAIN_LINEAR)
|
||||
current_volume -= linear_dropoff
|
||||
if(SUSTAIN_EXPONENTIAL)
|
||||
current_volume /= exponential_dropoff
|
||||
channels_playing[channel] = current_volume
|
||||
var/dead = current_volume <= sustain_dropoff_volume
|
||||
var/channelnumber = text2num(channel)
|
||||
if(dead)
|
||||
channels_playing -= channel
|
||||
channels_idle += channel
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.stop_sound_channel(channelnumber)
|
||||
else
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.set_sound_channel_volume(channelnumber, current_volume)
|
||||
@@ -835,9 +835,9 @@
|
||||
|
||||
force = CLAMP((ghost_counter * 4), 0, 75)
|
||||
user.visible_message("<span class='danger'>[user] strikes with the force of [ghost_counter] vengeful spirits!</span>")
|
||||
..()
|
||||
return ..()
|
||||
|
||||
/obj/item/melee/ghost_sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
/obj/item/melee/ghost_sword/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
var/ghost_counter = ghost_check()
|
||||
final_block_chance += CLAMP((ghost_counter * 5), 0, 75)
|
||||
owner.visible_message("<span class='danger'>[owner] is protected by a ring of [ghost_counter] ghosts!</span>")
|
||||
|
||||
@@ -87,14 +87,6 @@
|
||||
return
|
||||
. = ..()
|
||||
|
||||
/obj/machinery/computer/shuttle/mining/common
|
||||
name = "lavaland shuttle console"
|
||||
desc = "Used to call and send the lavaland shuttle."
|
||||
req_access = list()
|
||||
circuit = /obj/item/circuitboard/computer/mining_shuttle/common
|
||||
shuttleId = "mining_common"
|
||||
possible_destinations = "lavaland_common_away;commonmining_home"
|
||||
|
||||
/**********************Mining car (Crate like thing, not the rail car)**************************/
|
||||
|
||||
/obj/structure/closet/crate/miningcar
|
||||
|
||||
@@ -276,7 +276,23 @@ Works together with spawning an observer, noted above.
|
||||
if (!(client.prefs.chat_toggles & CHAT_OOC))
|
||||
client.prefs.chat_toggles ^= CHAT_OOC
|
||||
transfer_ckey(ghost, FALSE)
|
||||
ghost.AddElement(/datum/element/ghost_role_eligibility,penalize) // technically already run earlier, but this adds the penalty
|
||||
if(penalize)
|
||||
var/penalty = CONFIG_GET(number/suicide_reenter_round_timer) MINUTES
|
||||
var/roundstart_quit_limit = CONFIG_GET(number/roundstart_suicide_time_limit) MINUTES
|
||||
if(world.time < roundstart_quit_limit) //add up the time difference to their antag rolling penalty if they quit before half a (ingame) hour even passed.
|
||||
penalty += roundstart_quit_limit - world.time
|
||||
if(penalty)
|
||||
penalty += world.realtime
|
||||
if(SSautotransfer.can_fire && SSautotransfer.maxvotes)
|
||||
var/maximumRoundEnd = SSautotransfer.starttime + SSautotransfer.voteinterval * SSautotransfer.maxvotes
|
||||
if(penalty - SSshuttle.realtimeofstart > maximumRoundEnd + SSshuttle.emergencyCallTime + SSshuttle.emergencyDockTime + SSshuttle.emergencyEscapeTime)
|
||||
penalty = CANT_REENTER_ROUND
|
||||
if(!(ckey in GLOB.client_ghost_timeouts))
|
||||
GLOB.client_ghost_timeouts += ckey
|
||||
GLOB.client_ghost_timeouts[ckey] = 0
|
||||
else if(GLOB.client_ghost_timeouts[ckey] == CANT_REENTER_ROUND)
|
||||
return
|
||||
GLOB.client_ghost_timeouts[ckey] = max(GLOB.client_ghost_timeouts[ckey],penalty)
|
||||
// needs to be done AFTER the ckey transfer, too
|
||||
return ghost
|
||||
|
||||
|
||||
@@ -63,13 +63,13 @@
|
||||
if(hit_atom)
|
||||
if(isliving(hit_atom))
|
||||
var/mob/living/L = hit_atom
|
||||
if(!L.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK))
|
||||
if(L.run_block(src, 0, "the [name]", ATTACK_TYPE_TACKLE, 0, src) & BLOCK_SUCCESS)
|
||||
DefaultCombatKnockdown(40, 1, 1)
|
||||
else
|
||||
L.visible_message("<span class ='danger'>[src] pounces on [L]!</span>", "<span class ='userdanger'>[src] pounces on you!</span>")
|
||||
L.DefaultCombatKnockdown(100)
|
||||
sleep(2)//Runtime prevention (infinite bump() calls on hulks)
|
||||
step_towards(src,L)
|
||||
else
|
||||
DefaultCombatKnockdown(40, 1, 1)
|
||||
|
||||
toggle_leap(0)
|
||||
else if(hit_atom.density && !hit_atom.CanPass(src))
|
||||
|
||||
@@ -984,14 +984,121 @@
|
||||
|
||||
/mob/living/carbon/vv_get_dropdown()
|
||||
. = ..()
|
||||
. += "---"
|
||||
.["Make AI"] = "?_src_=vars;[HrefToken()];makeai=[REF(src)]"
|
||||
.["Modify bodypart"] = "?_src_=vars;[HrefToken()];editbodypart=[REF(src)]"
|
||||
.["Modify organs"] = "?_src_=vars;[HrefToken()];editorgans=[REF(src)]"
|
||||
.["Hallucinate"] = "?_src_=vars;[HrefToken()];hallucinate=[REF(src)]"
|
||||
.["Give martial arts"] = "?_src_=vars;[HrefToken()];givemartialart=[REF(src)]"
|
||||
.["Give brain trauma"] = "?_src_=vars;[HrefToken()];givetrauma=[REF(src)]"
|
||||
.["Cure brain traumas"] = "?_src_=vars;[HrefToken()];curetraumas=[REF(src)]"
|
||||
VV_DROPDOWN_OPTION("", "---------")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MAKE_AI, "Make AI")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MODIFY_BODYPART, "Modify bodypart")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MODIFY_ORGANS, "Modify organs")
|
||||
VV_DROPDOWN_OPTION(VV_HK_HALLUCINATION, "Hallucinate")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MARTIAL_ART, "Give Martial Arts")
|
||||
VV_DROPDOWN_OPTION(VV_HK_GIVE_TRAUMA, "Give Brain Trauma")
|
||||
VV_DROPDOWN_OPTION(VV_HK_CURE_TRAUMA, "Cure Brain Traumas")
|
||||
|
||||
/mob/living/carbon/vv_do_topic(list/href_list)
|
||||
. = ..()
|
||||
if(href_list[VV_HK_MODIFY_BODYPART])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
var/edit_action = input(usr, "What would you like to do?","Modify Body Part") as null|anything in list("add","remove", "augment")
|
||||
if(!edit_action)
|
||||
return
|
||||
var/list/limb_list = list()
|
||||
if(edit_action == "remove" || edit_action == "augment")
|
||||
for(var/obj/item/bodypart/B in bodyparts)
|
||||
limb_list += B.body_zone
|
||||
if(edit_action == "remove")
|
||||
limb_list -= BODY_ZONE_CHEST
|
||||
else
|
||||
limb_list = list(BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
|
||||
for(var/obj/item/bodypart/B in bodyparts)
|
||||
limb_list -= B.body_zone
|
||||
var/result = input(usr, "Please choose which body part to [edit_action]","[capitalize(edit_action)] Body Part") as null|anything in limb_list
|
||||
if(result)
|
||||
var/obj/item/bodypart/BP = get_bodypart(result)
|
||||
switch(edit_action)
|
||||
if("remove")
|
||||
if(BP)
|
||||
BP.drop_limb()
|
||||
else
|
||||
to_chat(usr, "[src] doesn't have such bodypart.")
|
||||
if("add")
|
||||
if(BP)
|
||||
to_chat(usr, "[src] already has such bodypart.")
|
||||
else
|
||||
if(!regenerate_limb(result))
|
||||
to_chat(usr, "[src] cannot have such bodypart.")
|
||||
if("augment")
|
||||
if(ishuman(src))
|
||||
if(BP)
|
||||
BP.change_bodypart_status(BODYPART_ROBOTIC, TRUE, TRUE)
|
||||
else
|
||||
to_chat(usr, "[src] doesn't have such bodypart.")
|
||||
else
|
||||
to_chat(usr, "Only humans can be augmented.")
|
||||
admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [src]")
|
||||
if(href_list[VV_HK_MAKE_AI])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
|
||||
return
|
||||
usr.client.holder.Topic("vv_override", list("makeai"=href_list[VV_HK_TARGET]))
|
||||
if(href_list[VV_HK_MODIFY_ORGANS])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
usr.client.manipulate_organs(src)
|
||||
if(href_list[VV_HK_MARTIAL_ART])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
var/list/artpaths = subtypesof(/datum/martial_art)
|
||||
var/list/artnames = list()
|
||||
for(var/i in artpaths)
|
||||
var/datum/martial_art/M = i
|
||||
artnames[initial(M.name)] = M
|
||||
var/result = input(usr, "Choose the martial art to teach","JUDO CHOP") as null|anything in artnames
|
||||
if(!usr)
|
||||
return
|
||||
if(QDELETED(src))
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
return
|
||||
if(result)
|
||||
var/chosenart = artnames[result]
|
||||
var/datum/martial_art/MA = new chosenart
|
||||
MA.teach(src)
|
||||
log_admin("[key_name(usr)] has taught [MA] to [key_name(src)].")
|
||||
message_admins("<span class='notice'>[key_name_admin(usr)] has taught [MA] to [key_name_admin(src)].</span>")
|
||||
if(href_list[VV_HK_GIVE_TRAUMA])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
var/list/traumas = subtypesof(/datum/brain_trauma)
|
||||
var/result = input(usr, "Choose the brain trauma to apply","Traumatize") as null|anything in traumas
|
||||
if(!usr)
|
||||
return
|
||||
if(QDELETED(src))
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
return
|
||||
if(!result)
|
||||
return
|
||||
var/datum/brain_trauma/BT = gain_trauma(result)
|
||||
if(BT)
|
||||
log_admin("[key_name(usr)] has traumatized [key_name(src)] with [BT.name]")
|
||||
message_admins("<span class='notice'>[key_name_admin(usr)] has traumatized [key_name_admin(src)] with [BT.name].</span>")
|
||||
if(href_list[VV_HK_CURE_TRAUMA])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
cure_all_traumas(TRAUMA_RESILIENCE_ABSOLUTE)
|
||||
log_admin("[key_name(usr)] has cured all traumas from [key_name(src)].")
|
||||
message_admins("<span class='notice'>[key_name_admin(usr)] has cured all traumas from [key_name_admin(src)].</span>")
|
||||
if(href_list[VV_HK_HALLUCINATION])
|
||||
if(!check_rights(NONE))
|
||||
return
|
||||
var/list/hallucinations = subtypesof(/datum/hallucination)
|
||||
var/result = input(usr, "Choose the hallucination to apply","Send Hallucination") as null|anything in hallucinations
|
||||
if(!usr)
|
||||
return
|
||||
if(QDELETED(src))
|
||||
to_chat(usr, "Mob doesn't exist anymore")
|
||||
return
|
||||
if(result)
|
||||
new result(src, TRUE)
|
||||
|
||||
/mob/living/carbon/can_resist()
|
||||
return bodyparts.len > 2 && ..()
|
||||
|
||||
@@ -86,13 +86,10 @@
|
||||
if(!(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
|
||||
totitemdamage *= 1.5
|
||||
//CIT CHANGES END HERE
|
||||
if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration))
|
||||
var/impacting_zone = (user == src)? check_zone(user.zone_selected) : ran_zone(user.zone_selected)
|
||||
if((user != src) && (run_block(I, totitemdamage, "the [I]", ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone) & BLOCK_SUCCESS))
|
||||
return FALSE
|
||||
var/obj/item/bodypart/affecting
|
||||
if(user == src)
|
||||
affecting = get_bodypart(check_zone(user.zone_selected)) //we're self-mutilating! yay!
|
||||
else
|
||||
affecting = get_bodypart(ran_zone(user.zone_selected))
|
||||
var/obj/item/bodypart/affecting = get_bodypart(impacting_zone)
|
||||
if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest)
|
||||
affecting = bodyparts[1]
|
||||
SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting)
|
||||
|
||||
@@ -849,15 +849,95 @@
|
||||
|
||||
/mob/living/carbon/human/vv_get_dropdown()
|
||||
. = ..()
|
||||
. += "---"
|
||||
.["Make monkey"] = "?_src_=vars;[HrefToken()];makemonkey=[REF(src)]"
|
||||
.["Set Species"] = "?_src_=vars;[HrefToken()];setspecies=[REF(src)]"
|
||||
.["Make cyborg"] = "?_src_=vars;[HrefToken()];makerobot=[REF(src)]"
|
||||
.["Make alien"] = "?_src_=vars;[HrefToken()];makealien=[REF(src)]"
|
||||
.["Make slime"] = "?_src_=vars;[HrefToken()];makeslime=[REF(src)]"
|
||||
.["Toggle Purrbation"] = "?_src_=vars;[HrefToken()];purrbation=[REF(src)]"
|
||||
.["Copy outfit"] = "?_src_=vars;[HrefToken()];copyoutfit=[REF(src)]"
|
||||
.["Add/Remove Quirks"] = "?_src_=vars;[HrefToken()];modquirks=[REF(src)]"
|
||||
VV_DROPDOWN_OPTION("", "---------")
|
||||
VV_DROPDOWN_OPTION(VV_HK_COPY_OUTFIT, "Copy Outfit")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MOD_QUIRKS, "Add/Remove Quirks")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MAKE_MONKEY, "Make Monkey")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MAKE_CYBORG, "Make Cyborg")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MAKE_SLIME, "Make Slime")
|
||||
VV_DROPDOWN_OPTION(VV_HK_MAKE_ALIEN, "Make Alien")
|
||||
VV_DROPDOWN_OPTION(VV_HK_SET_SPECIES, "Set Species")
|
||||
VV_DROPDOWN_OPTION(VV_HK_PURRBATION, "Toggle Purrbation")
|
||||
|
||||
/mob/living/carbon/human/vv_do_topic(list/href_list)
|
||||
. = ..()
|
||||
if(href_list[VV_HK_COPY_OUTFIT])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
copy_outfit()
|
||||
if(href_list[VV_HK_MOD_QUIRKS])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
|
||||
var/list/options = list("Clear"="Clear")
|
||||
for(var/x in subtypesof(/datum/quirk))
|
||||
var/datum/quirk/T = x
|
||||
var/qname = initial(T.name)
|
||||
options[has_quirk(T) ? "[qname] (Remove)" : "[qname] (Add)"] = T
|
||||
|
||||
var/result = input(usr, "Choose quirk to add/remove","Quirk Mod") as null|anything in options
|
||||
if(result)
|
||||
if(result == "Clear")
|
||||
for(var/datum/quirk/q in roundstart_quirks)
|
||||
remove_quirk(q.type)
|
||||
else
|
||||
var/T = options[result]
|
||||
if(has_quirk(T))
|
||||
remove_quirk(T)
|
||||
else
|
||||
add_quirk(T,TRUE)
|
||||
if(href_list[VV_HK_MAKE_MONKEY])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
|
||||
return
|
||||
usr.client.holder.Topic("vv_override", list("monkeyone"=href_list[VV_HK_TARGET]))
|
||||
if(href_list[VV_HK_MAKE_CYBORG])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
|
||||
return
|
||||
usr.client.holder.Topic("vv_override", list("makerobot"=href_list[VV_HK_TARGET]))
|
||||
if(href_list[VV_HK_MAKE_ALIEN])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
|
||||
return
|
||||
usr.client.holder.Topic("vv_override", list("makealien"=href_list[VV_HK_TARGET]))
|
||||
if(href_list[VV_HK_MAKE_SLIME])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
|
||||
return
|
||||
usr.client.holder.Topic("vv_override", list("makeslime"=href_list[VV_HK_TARGET]))
|
||||
if(href_list[VV_HK_SET_SPECIES])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list
|
||||
if(result)
|
||||
var/newtype = GLOB.species_list[result]
|
||||
admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [src] to [result]")
|
||||
set_species(newtype)
|
||||
if(href_list[VV_HK_PURRBATION])
|
||||
if(!check_rights(R_SPAWN))
|
||||
return
|
||||
if(!ishumanbasic(src))
|
||||
to_chat(usr, "This can only be done to the basic human species at the moment.")
|
||||
return
|
||||
var/success = purrbation_toggle(src)
|
||||
if(success)
|
||||
to_chat(usr, "Put [src] on purrbation.")
|
||||
log_admin("[key_name(usr)] has put [key_name(src)] on purrbation.")
|
||||
var/msg = "<span class='notice'>[key_name_admin(usr)] has put [key_name(src)] on purrbation.</span>"
|
||||
message_admins(msg)
|
||||
admin_ticket_log(src, msg)
|
||||
|
||||
else
|
||||
to_chat(usr, "Removed [src] from purrbation.")
|
||||
log_admin("[key_name(usr)] has removed [key_name(src)] from purrbation.")
|
||||
var/msg = "<span class='notice'>[key_name_admin(usr)] has removed [key_name(src)] from purrbation.</span>"
|
||||
message_admins(msg)
|
||||
admin_ticket_log(src, msg)
|
||||
|
||||
/mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user)
|
||||
if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/mob/living/carbon/human/get_blocking_items()
|
||||
. = ..()
|
||||
if(wear_suit)
|
||||
. += wear_suit
|
||||
if(w_uniform)
|
||||
. += w_uniform
|
||||
if(wear_neck)
|
||||
. += wear_neck
|
||||
@@ -52,36 +52,12 @@
|
||||
return martial_art_result
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/check_reflect(def_zone)
|
||||
if(wear_suit?.IsReflect(def_zone))
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
var/block_chance_modifier = round(damage / -3)
|
||||
if(wear_suit)
|
||||
var/final_block_chance = wear_suit.block_chance - (CLAMP((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier
|
||||
if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
|
||||
return TRUE
|
||||
if(w_uniform)
|
||||
var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier
|
||||
if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
|
||||
return TRUE
|
||||
if(wear_neck)
|
||||
var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier
|
||||
if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/carbon/human/can_embed(obj/item/I)
|
||||
if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/carbon/human/proc/check_block()
|
||||
/mob/living/carbon/human/proc/check_martial_melee_block()
|
||||
if(mind)
|
||||
if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE))
|
||||
return TRUE
|
||||
|
||||
@@ -1437,7 +1437,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
to_chat(user, "<span class='notice'>You do not breathe, so you cannot perform CPR.</span>")
|
||||
|
||||
/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
|
||||
if(target.check_block())
|
||||
if(target.check_martial_melee_block())
|
||||
target.visible_message("<span class='warning'>[target] blocks [user]'s grab attempt!</span>")
|
||||
return 0
|
||||
if(attacker_style && attacker_style.grab_act(user,target))
|
||||
@@ -1453,9 +1453,15 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(IS_STAMCRIT(user)) //CITADEL CHANGE - makes it impossible to punch while in stamina softcrit
|
||||
to_chat(user, "<span class='warning'>You're too exhausted.</span>") //CITADEL CHANGE - ditto
|
||||
return FALSE //CITADEL CHANGE - ditto
|
||||
if(target.check_block())
|
||||
if(target.check_martial_melee_block())
|
||||
target.visible_message("<span class='warning'>[target] blocks [user]'s attack!</span>")
|
||||
return FALSE
|
||||
|
||||
if(HAS_TRAIT(user, TRAIT_PUGILIST))//CITADEL CHANGE - makes punching cause staminaloss but funny martial artist types get a discount
|
||||
user.adjustStaminaLossBuffered(1.5)
|
||||
else
|
||||
user.adjustStaminaLossBuffered(3.5)
|
||||
|
||||
if(attacker_style && attacker_style.harm_act(user,target))
|
||||
return TRUE
|
||||
else
|
||||
@@ -1474,13 +1480,15 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
else
|
||||
user.do_attack_animation(target, ATTACK_EFFECT_PUNCH)
|
||||
|
||||
user.adjustStaminaLossBuffered(5) //CITADEL CHANGE - makes punching cause staminaloss
|
||||
|
||||
var/damage = rand(user.dna.species.punchdamagelow, user.dna.species.punchdamagehigh)
|
||||
var/puncherstam = user.getStaminaLoss()
|
||||
var/puncherbrute = user.getBruteLoss()
|
||||
var/punchedstam = target.getStaminaLoss()
|
||||
var/punchedbrute = target.getBruteLoss()
|
||||
|
||||
//CITADEL CHANGES - makes resting and disabled combat mode reduce punch damage, makes being out of combat mode result in you taking more damage
|
||||
if(!(target.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE) && damage < user.dna.species.punchstunthreshold)
|
||||
damage = user.dna.species.punchstunthreshold - 1
|
||||
if(!(target.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
|
||||
damage *= 1.5
|
||||
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
|
||||
damage *= 0.5
|
||||
if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
|
||||
@@ -1494,7 +1502,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(atk_verb == ATTACK_EFFECT_KICK) //kicks never miss (provided your species deals more than 0 damage)
|
||||
miss_chance = 0
|
||||
else
|
||||
miss_chance = min((user.dna.species.punchdamagehigh/user.dna.species.punchdamagelow) + user.getStaminaLoss() + (user.getBruteLoss()*0.5), 100) //old base chance for a miss + various damage. capped at 100 to prevent weirdness in prob()
|
||||
miss_chance = min(10 + ((puncherstam + puncherbrute)*0.5), 100) //probability of miss has a base of 10, and modified based on half your stamina and brute total. Capped at max 100 and min 0 to prevent weirdness in prob()
|
||||
|
||||
if(!damage || !affecting || prob(miss_chance))//future-proofing for species that have 0 damage/weird cases where no zone is targeted
|
||||
playsound(target.loc, user.dna.species.miss_sound, 25, TRUE, -1)
|
||||
@@ -1529,13 +1537,25 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
log_combat(user, target, "punched")
|
||||
|
||||
if((target.stat != DEAD) && damage >= user.dna.species.punchstunthreshold)
|
||||
target.visible_message("<span class='danger'>[user] knocks [target] down!</span>", \
|
||||
"<span class='userdanger'>You're knocked down by [user]!</span>", "<span class='hear'>You hear aggressive shuffling followed by a loud thud!</span>", COMBAT_MESSAGE_RANGE, user)
|
||||
to_chat(user, "<span class='danger'>You knock [target] down!</span>")
|
||||
var/knockdown_duration = 40 + (target.getStaminaLoss() + (target.getBruteLoss()*0.5))*0.8 - armor_block
|
||||
target.DefaultCombatKnockdown(knockdown_duration)
|
||||
target.forcesay(GLOB.hit_appends)
|
||||
log_combat(user, target, "got a stun punch with their previous punch")
|
||||
if((punchedstam > 50) && prob(punchedstam*0.5)) //If our punch victim has been hit above the threshold, and they have more than 50 stamina damage, roll for stun, probability of 1% per 2 stamina damage
|
||||
|
||||
target.visible_message("<span class='danger'>[user] knocks [target] down!</span>", \
|
||||
"<span class='userdanger'>You're knocked down by [user]!</span>", "<span class='hear'>You hear aggressive shuffling followed by a loud thud!</span>", COMBAT_MESSAGE_RANGE, user)
|
||||
to_chat(user, "<span class='danger'>You knock [target] down!</span>")
|
||||
|
||||
var/knockdown_duration = 40 + (punchedstam + (punchedbrute*0.5))*0.8 - armor_block
|
||||
target.DefaultCombatKnockdown(knockdown_duration)
|
||||
target.forcesay(GLOB.hit_appends)
|
||||
log_combat(user, target, "got a stun punch with their previous punch")
|
||||
|
||||
if(HAS_TRAIT(user, TRAIT_KI_VAMPIRE) && !HAS_TRAIT(target, TRAIT_NOBREATH) && (punchedbrute < 100)) //If we're a ki vampire we also sap them of lifeforce, but only if they're not too beat up. Also living organics only.
|
||||
user.adjustBruteLoss(-5)
|
||||
user.adjustFireLoss(-5)
|
||||
user.adjustStaminaLoss(-20)
|
||||
|
||||
target.adjustCloneLoss(10)
|
||||
target.adjustBruteLoss(10)
|
||||
|
||||
else if(!(target.mobility_flags & MOBILITY_STAND))
|
||||
target.forcesay(GLOB.hit_appends)
|
||||
|
||||
@@ -1552,6 +1572,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
var/aim_for_groin = user.zone_selected == "groin"
|
||||
var/target_aiming_for_groin = target.zone_selected == "groin"
|
||||
|
||||
if(target.check_martial_melee_block()) //END EDIT
|
||||
target.visible_message("<span class='warning'>[target] blocks [user]'s disarm attempt!</span>")
|
||||
return FALSE
|
||||
if(IS_STAMCRIT(user))
|
||||
to_chat(user, "<span class='warning'>You're too exhausted!</span>")
|
||||
return FALSE
|
||||
@@ -1680,9 +1703,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
|
||||
// Allows you to put in item-specific reactions based on species
|
||||
if(user != H)
|
||||
if(H.check_shields(I, I.force, "the [I.name]", MELEE_ATTACK, I.armour_penetration))
|
||||
if(H.run_block(I, I.force, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone) & BLOCK_SUCCESS)
|
||||
return 0
|
||||
if(H.check_block())
|
||||
if(H.check_martial_melee_block())
|
||||
H.visible_message("<span class='warning'>[H] blocks [I]!</span>")
|
||||
return 0
|
||||
|
||||
@@ -1798,7 +1821,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
return TRUE
|
||||
if(M.mind)
|
||||
attacker_style = M.mind.martial_art
|
||||
if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK))
|
||||
if((M != H) && M.a_intent != INTENT_HELP && (H.run_block(M, 0, "[M]", ATTACK_TYPE_UNARMED, 0, M, M.zone_selected) & BLOCK_SUCCESS))
|
||||
log_combat(M, H, "attempted to touch")
|
||||
H.visible_message("<span class='warning'>[M] attempted to touch [H]!</span>")
|
||||
return TRUE
|
||||
@@ -1836,7 +1859,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
|
||||
if(IS_STAMCRIT(user))
|
||||
to_chat(user, "<span class='warning'>You're too exhausted.</span>")
|
||||
return FALSE
|
||||
if(target.check_block())
|
||||
if(target.check_martial_melee_block())
|
||||
target.visible_message("<span class='warning'>[target] blocks [user]'s shoving attempt!</span>")
|
||||
return FALSE
|
||||
if(attacker_style && attacker_style.disarm_act(user,target))
|
||||
|
||||
@@ -92,6 +92,8 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
|
||||
//These count in on_life ticks which should be 2 seconds per every increment of 1 in a perfect world.
|
||||
var/dwarf_filth_ticker = 0 //Currently set =< 4, that means this will fire the proc around every 4-8 seconds.
|
||||
var/dwarf_eth_ticker = 0 //Currently set =< 1, that means this will fire the proc around every 2 seconds
|
||||
var/last_filth_spam
|
||||
var/last_alcohol_spam
|
||||
|
||||
/obj/item/organ/dwarfgland/prepare_eat()
|
||||
var/obj/S = ..()
|
||||
@@ -136,40 +138,39 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
|
||||
filth_counter += 10 //Dwarves could technically chainstun each other in a vomit tantrum spiral.
|
||||
switch(filth_counter)
|
||||
if(11 to 25)
|
||||
if(prob(25))
|
||||
to_chat(owner, "<span class = 'danger'>Someone should really clean up in here!</span>")
|
||||
if(last_filth_spam + 40 SECONDS < world.time)
|
||||
to_chat(owner, "<span class = 'warning'>Someone should really clean up in here!</span>")
|
||||
last_filth_spam = world.time
|
||||
if(26 to 50)
|
||||
if(prob(30)) //Probability the message appears
|
||||
if(prob(6)) //And then the probability they vomit along with it.
|
||||
to_chat(owner, "<span class = 'danger'>The stench makes you queasy.</span>")
|
||||
if(prob(20)) //And then the probability they vomit along with it.
|
||||
owner.vomit(20) //I think vomit should stay over a disgust adjustment.
|
||||
owner.vomit(10) //I think vomit should stay over a disgust adjustment.
|
||||
if(51 to 75)
|
||||
if(prob(35))
|
||||
if(prob(9))
|
||||
to_chat(owner, "<span class = 'danger'>By Armok! You won't be able to keep alcohol down at all!</span>")
|
||||
if(prob(25))
|
||||
owner.vomit(20) //Its more funny
|
||||
owner.vomit(20) //Its more funny
|
||||
if(76 to 100)
|
||||
if(prob(40))
|
||||
if(prob(11))
|
||||
to_chat(owner, "<span class = 'userdanger'>You can't live in such FILTH!</span>")
|
||||
if(prob(25))
|
||||
owner.adjustToxLoss(10) //Now they start dying.
|
||||
owner.vomit(20)
|
||||
owner.adjustToxLoss(10) //Now they start dying.
|
||||
owner.vomit(20)
|
||||
if(101 to INFINITY) //Now they will really start dying
|
||||
if(prob(40))
|
||||
if(last_filth_spam + 12 SECONDS < world.time)
|
||||
to_chat(owner, "<span class = 'userdanger'> THERES TOO MUCH FILTH, OH GODS THE FILTH!</span>")
|
||||
last_filth_spam = world.time
|
||||
if(prob(40))
|
||||
owner.adjustToxLoss(15)
|
||||
owner.vomit(40)
|
||||
owner.vomit(30)
|
||||
CHECK_TICK //Check_tick right here, its motherfuckin magic. (To me at least)
|
||||
|
||||
//Handles the dwarf alcohol cycle tied to on_life, it ticks in dwarf_cycle_ticker.
|
||||
/obj/item/organ/dwarfgland/proc/dwarf_eth_cycle()
|
||||
//BOOZE POWER
|
||||
var/init_stored_alcohol = stored_alcohol
|
||||
for(var/datum/reagent/R in owner.reagents.reagent_list)
|
||||
if(istype(R, /datum/reagent/consumable/ethanol))
|
||||
var/datum/reagent/consumable/ethanol/E = R
|
||||
stored_alcohol += (E.boozepwr / 50)
|
||||
if(stored_alcohol > max_alcohol) //Dwarves technically start at 250 alcohol stored.
|
||||
stored_alcohol = max_alcohol
|
||||
stored_alcohol = CLAMP(stored_alcohol + E.boozepwr / 50, 0, max_alcohol)
|
||||
var/heal_amt = heal_rate
|
||||
stored_alcohol -= alcohol_rate //Subtracts alcohol_Rate from stored alcohol so EX: 250 - 0.25 per each loop that occurs.
|
||||
if(stored_alcohol > 400) //If they are over 400 they start regenerating
|
||||
@@ -177,16 +178,27 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) //
|
||||
owner.adjustFireLoss(-heal_amt) //Unless they drink casually all the time.
|
||||
owner.adjustOxyLoss(-heal_amt)
|
||||
owner.adjustCloneLoss(-heal_amt) //Also they will probably get brain damage if thats a thing here.
|
||||
if(prob(25))
|
||||
switch(stored_alcohol)
|
||||
if(0 to 24)
|
||||
if(init_stored_alcohol + 0.5 < stored_alcohol) //recovering stored alcohol at a steady rate of +0.75, no spam.
|
||||
return
|
||||
switch(stored_alcohol)
|
||||
if(0 to 24)
|
||||
if(last_alcohol_spam + 8 SECONDS < world.time)
|
||||
to_chat(owner, "<span class='userdanger'>DAMNATION INCARNATE, WHY AM I CURSED WITH THIS DRY-SPELL? I MUST DRINK.</span>")
|
||||
owner.adjustToxLoss(35)
|
||||
if(25 to 50)
|
||||
last_alcohol_spam = world.time
|
||||
owner.adjustToxLoss(10)
|
||||
if(25 to 50)
|
||||
if(last_alcohol_spam + 20 SECONDS < world.time)
|
||||
to_chat(owner, "<span class='danger'>Oh DAMN, I need some brew!</span>")
|
||||
if(51 to 75)
|
||||
last_alcohol_spam = world.time
|
||||
if(51 to 75)
|
||||
if(last_alcohol_spam + 35 SECONDS < world.time)
|
||||
to_chat(owner, "<span class='warning'>Your body aches, you need to get ahold of some booze...</span>")
|
||||
if(76 to 100)
|
||||
last_alcohol_spam = world.time
|
||||
if(76 to 100)
|
||||
if(last_alcohol_spam + 40 SECONDS < world.time)
|
||||
to_chat(owner, "<span class='notice'>A pint of anything would really hit the spot right now.</span>")
|
||||
if(101 to 150)
|
||||
last_alcohol_spam = world.time
|
||||
if(101 to 150)
|
||||
if(last_alcohol_spam + 50 SECONDS < world.time)
|
||||
to_chat(owner, "<span class='notice'>You feel like you could use a good brew.</span>")
|
||||
last_alcohol_spam = world.time
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
siemens_coeff = 0
|
||||
punchdamagelow = 5
|
||||
punchdamagehigh = 14
|
||||
punchstunthreshold = 11 //about 40% chance to stun
|
||||
punchstunthreshold = 10
|
||||
no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM, SLOT_S_STORE)
|
||||
nojumpsuit = 1
|
||||
sexes = 1
|
||||
@@ -168,7 +168,7 @@
|
||||
name = "Silver Golem"
|
||||
id = "silver golem"
|
||||
fixed_mut_color = "ddd"
|
||||
punchstunthreshold = 9 //60% chance, from 40%
|
||||
punchstunthreshold = 9
|
||||
meat = /obj/item/stack/ore/silver
|
||||
info_text = "As a <span class='danger'>Silver Golem</span>, your attacks have a higher chance of stunning. Being made of silver, your body is immune to most types of magic."
|
||||
prefix = "Silver"
|
||||
@@ -190,7 +190,7 @@
|
||||
stunmod = 0.4
|
||||
punchdamagelow = 12
|
||||
punchdamagehigh = 21
|
||||
punchstunthreshold = 18 //still 40% stun chance
|
||||
punchstunthreshold = 18
|
||||
speedmod = 4 //pretty fucking slow
|
||||
meat = /obj/item/stack/ore/iron
|
||||
info_text = "As a <span class='danger'>Plasteel Golem</span>, you are slower, but harder to stun, and hit very hard when punching."
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
inherent_traits = list(TRAIT_NOBREATH)
|
||||
speedmod = 1.5 //faster than golems but not by much
|
||||
|
||||
punchdamagelow = 6
|
||||
punchdamagehigh = 14
|
||||
punchstunthreshold = 14 //about 44% chance to stun
|
||||
punchdamagelow = 2
|
||||
punchdamagehigh = 12 //still better than humans
|
||||
punchstunthreshold = 10
|
||||
|
||||
no_equip = list(SLOT_WEAR_MASK, SLOT_WEAR_SUIT, SLOT_GLOVES, SLOT_SHOES, SLOT_W_UNIFORM)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
armor = 25
|
||||
punchdamagelow = 10
|
||||
punchdamagehigh = 19
|
||||
punchstunthreshold = 14 //about 50% chance to stun
|
||||
punchstunthreshold = 14
|
||||
disguise_fail_health = 50
|
||||
|
||||
/datum/species/synth/on_species_gain(mob/living/carbon/human/H, datum/species/old_species)
|
||||
|
||||
@@ -146,5 +146,7 @@
|
||||
if(transfer_name)
|
||||
H.name = caster.name
|
||||
|
||||
clothes_req = 0
|
||||
human_req = 0
|
||||
|
||||
clothes_req = NONE
|
||||
mobs_whitelist = null
|
||||
mobs_blacklist = null
|
||||
|
||||
@@ -377,9 +377,15 @@
|
||||
|
||||
var/turf/open/miasma_turf = deceasedturf
|
||||
|
||||
var/list/cached_gases = miasma_turf.air.gases
|
||||
var/datum/gas_mixture/stank = new
|
||||
|
||||
cached_gases[/datum/gas/miasma] += 0.1
|
||||
stank.gases[/datum/gas/miasma] = 0.1
|
||||
|
||||
stank.temperature = BODYTEMP_NORMAL
|
||||
|
||||
miasma_turf.assume_air(stank)
|
||||
|
||||
miasma_turf.air_update_turf()
|
||||
|
||||
/mob/living/carbon/proc/handle_blood()
|
||||
return
|
||||
|
||||
@@ -1198,3 +1198,19 @@
|
||||
gender = ngender
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/vv_get_header()
|
||||
. = ..()
|
||||
var/refid = REF(src)
|
||||
. += {"
|
||||
<br><font size='1'>[VV_HREF_TARGETREF_1V(refid, VV_HK_BASIC_EDIT, "[ckey || "no ckey"]", NAMEOF(src, ckey))] / [VV_HREF_TARGETREF_1V(refid, VV_HK_BASIC_EDIT, "[real_name || "no real name"]", NAMEOF(src, real_name))]</font>
|
||||
<br><font size='1'>
|
||||
BRUTE:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=brute' id='brute'>[getBruteLoss()]</a>
|
||||
FIRE:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=fire' id='fire'>[getFireLoss()]</a>
|
||||
TOXIN:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=toxin' id='toxin'>[getToxLoss()]</a>
|
||||
OXY:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=oxygen' id='oxygen'>[getOxyLoss()]</a>
|
||||
CLONE:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=clone' id='clone'>[getCloneLoss()]</a>
|
||||
BRAIN:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=brain' id='brain'>[getOrganLoss(ORGAN_SLOT_BRAIN)]</a>
|
||||
STAMINA:<font size='1'><a href='?_src_=vars;[HrefToken()];mobToDamage=[refid];adjustDamage=stamina' id='stamina'>[getStaminaLoss()]</a>
|
||||
</font>
|
||||
"}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// This file has a weird name, but it's for anything related to the checks for shields, blocking, dodging, and similar "stop this attack before it actually impacts the target" as opposed to "defend once it has hit".
|
||||
|
||||
/*
|
||||
/// Bitflags for check_block() and handle_block(). Meant to be combined. You can be hit and still reflect, for example, if you do not use BLOCK_SUCCESS.
|
||||
/// Attack was not blocked
|
||||
#define BLOCK_NONE NONE
|
||||
/// Attack was blocked, do not do damage. THIS FLAG MUST BE THERE FOR DAMAGE/EFFECT PREVENTION!
|
||||
#define BLOCK_SUCCESS (1<<1)
|
||||
|
||||
/// The below are for "metadata" on "how" the attack was blocked.
|
||||
|
||||
/// Attack was and should be reflected (NOTE: the SHOULD here is important, as it says "the thing blocking isn't handling the reflecting for you so do it yourself"!)
|
||||
#define BLOCK_SHOULD_REFLECT (1<<2)
|
||||
/// Attack was manually redirected (including reflected) by any means by the defender. For when YOU are handling the reflection, rather than the thing hitting you. (see sleeping carp)
|
||||
#define BLOCK_REDIRECTED (1<<3)
|
||||
/// Attack was blocked by something like a shield.
|
||||
#define BLOCK_PHYSICAL_EXTERNAL (1<<4)
|
||||
/// Attack was blocked by something worn on you.
|
||||
#define BLOCK_PHYSICAL_INTERNAL (1<<5)
|
||||
/// Attack should pass through. Like SHOULD_REFLECT but for.. well, passing through harmlessly.
|
||||
#define BLOCK_SHOULD_PASSTHROUGH (1<<6)
|
||||
/// Attack outright missed because the target dodged. Should usually be combined with SHOULD_PASSTHROUGH or something (see martial arts)
|
||||
#define BLOCK_TARGET_DODGED (1<<7)
|
||||
*/
|
||||
|
||||
///Check whether or not we can block, without "triggering" a block. Basically run checks without effects like depleting shields. Wrapper for do_run_block(). The arguments on that means the same as for this.
|
||||
/mob/living/proc/check_block(atom/object, damage, attack_text = "the attack", attack_type, armour_penetration, mob/attacker, def_zone, list/return_list)
|
||||
return do_run_block(FALSE, object, damage, attack_text, attack_type, armour_penetration, attacker, check_zone(def_zone), return_list)
|
||||
|
||||
/// Runs a block "sequence", effectively checking and then doing effects if necessary. Wrapper for do_run_block(). The arguments on that means the same as for this.
|
||||
/mob/living/proc/run_block(atom/object, damage, attack_text = "the attack", attack_type, armour_penetration, mob/attacker, def_zone, list/return_list)
|
||||
return do_run_block(TRUE, object, damage, attack_text, attack_type, armour_penetration, attacker, check_zone(def_zone), return_list)
|
||||
|
||||
/** The actual proc for block checks. DO NOT USE THIS DIRECTLY UNLESS YOU HAVE VERY GOOD REASON TO. To reduce copypaste for differences between handling for real attacks and virtual checks.
|
||||
* Automatically checks all held items for /obj/item/proc/run_block() with the same parameters.
|
||||
* @params
|
||||
* real_attack - If this attack is real. This one is quirky; If it's real, run_block is called. If it's not, check_block is called and none of the regular checks happen, and this is effectively only useful
|
||||
* for populating return_list with blocking metadata.
|
||||
* object - Whatever /atom is actually hitting us, in essence. For example, projectile if gun, item if melee, structure/whatever if it's a thrown, etc.
|
||||
* damage - The nominal damage this would do if it was to hit. Obviously doesn't take into effect explosions/magic/similar things.. unless you implement it to raise the value.
|
||||
* attack_text - The text that this attack should show, in the context of something like "[src] blocks [attack_text]!"
|
||||
* attack_type - See __DEFINES/combat.dm - Attack types, to distinguish between, for example, someone throwing an item at us vs bashing us with it.
|
||||
* armour_penetration - 0-100 value of how effectively armor penetrating the attack should be.
|
||||
* attacker - Set to the mob attacking IF KNOWN. Do not expect this to always be set!
|
||||
* def_zone - The zone this'll impact.
|
||||
* return_list - If something wants to grab things from what items/whatever put into list/block_return on obj/item/run_block and the comsig, pass in a list so you can grab anything put in it after block runs.
|
||||
*/
|
||||
/mob/living/proc/do_run_block(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
|
||||
// Component signal block runs have highest priority.. for now.
|
||||
. = SEND_SIGNAL(src, COMSIG_LIVING_RUN_BLOCK, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
|
||||
if((. & BLOCK_SUCCESS) && !(. & BLOCK_CONTINUE_CHAIN))
|
||||
return
|
||||
var/list/obj/item/tocheck = get_blocking_items()
|
||||
sortTim(tocheck, /proc/cmp_item_block_priority_asc)
|
||||
// i don't like this
|
||||
var/block_chance_modifier = round(damage / -3)
|
||||
if(real_attack)
|
||||
for(var/obj/item/I in tocheck)
|
||||
// i don't like this too
|
||||
var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
|
||||
var/results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
. |= results
|
||||
if((results & BLOCK_SUCCESS) && !(results & BLOCK_CONTINUE_CHAIN))
|
||||
break
|
||||
else
|
||||
for(var/obj/item/I in tocheck)
|
||||
// i don't like this too
|
||||
var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
|
||||
I.check_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list)
|
||||
|
||||
/// Gets an unsortedlist of objects to run block checks on.
|
||||
/mob/living/proc/get_blocking_items()
|
||||
. = list()
|
||||
for(var/obj/item/I in held_items)
|
||||
// this is a bad check but i am not removing it until a better catchall is made
|
||||
if(istype(I, /obj/item/clothing))
|
||||
continue
|
||||
. |= I
|
||||
|
||||
/obj/item
|
||||
/// The 0% to 100% chance for the default implementation of random block rolls.
|
||||
var/block_chance = 0
|
||||
/// Block priority, higher means we check this higher in the "chain".
|
||||
var/block_priority = BLOCK_PRIORITY_DEFAULT
|
||||
|
||||
/// Runs block and returns flag for do_run_block to process.
|
||||
/obj/item/proc/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
SEND_SIGNAL(src, COMSIG_ITEM_RUN_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
if(prob(final_block_chance))
|
||||
owner.visible_message("<span class='danger'>[owner] blocks [attack_text] with [src]!</span>")
|
||||
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
|
||||
return BLOCK_NONE
|
||||
|
||||
/// Returns block information using list/block_return. Used for check_block() on mobs.
|
||||
/obj/item/proc/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
SEND_SIGNAL(src, COMSIG_ITEM_CHECK_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return)
|
||||
var/existing = block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE]
|
||||
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = max(existing || 0, final_block_chance)
|
||||
@@ -36,48 +36,44 @@
|
||||
/mob/living/proc/on_hit(obj/item/projectile/P)
|
||||
return BULLET_ACT_HIT
|
||||
|
||||
/mob/living/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0)
|
||||
var/block_chance_modifier = round(damage / -3)
|
||||
for(var/obj/item/I in held_items)
|
||||
if(!istype(I, /obj/item/clothing))
|
||||
var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example
|
||||
if(I.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/proc/check_reflect(def_zone) //Reflection checks for anything in your hands, based on the reflection chance of the object(s)
|
||||
for(var/obj/item/I in held_items)
|
||||
if(I.IsReflect(def_zone))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/proc/reflect_bullet_check(obj/item/projectile/P, def_zone)
|
||||
if(P.is_reflectable && check_reflect(def_zone)) // Checks if you've passed a reflection% check
|
||||
visible_message("<span class='danger'>The [P.name] gets reflected by [src]!</span>", \
|
||||
"<span class='userdanger'>The [P.name] gets reflected by [src]!</span>")
|
||||
// Find a turf near or on the original location to bounce to
|
||||
if(P.starting)
|
||||
var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
|
||||
var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
|
||||
var/turf/curloc = get_turf(src)
|
||||
// redirect the projectile
|
||||
P.original = locate(new_x, new_y, P.z)
|
||||
P.starting = curloc
|
||||
P.firer = src
|
||||
P.yo = new_y - curloc.y
|
||||
P.xo = new_x - curloc.x
|
||||
var/new_angle_s = P.Angle + rand(120,240)
|
||||
while(new_angle_s > 180) // Translate to regular projectile degrees
|
||||
new_angle_s -= 360
|
||||
P.setAngle(new_angle_s)
|
||||
return TRUE
|
||||
return FALSE
|
||||
/mob/living/proc/handle_projectile_attack_redirection(obj/item/projectile/P, redirection_mode, silent = FALSE)
|
||||
P.ignore_source_check = TRUE
|
||||
switch(redirection_mode)
|
||||
if(REDIRECT_METHOD_DEFLECT)
|
||||
P.setAngle(SIMPLIFY_DEGREES(P.Angle + rand(120, 240)))
|
||||
if(!silent)
|
||||
visible_message("<span class='danger'>[P] gets deflected by [src]!</span>", \
|
||||
"<span class='userdanger'>[P] gets deflected by [src]!</span>")
|
||||
if(REDIRECT_METHOD_REFLECT)
|
||||
P.setAngle(SIMPLIFY_DEGREES(P.Angle + 180))
|
||||
if(!silent)
|
||||
visible_message("<span class='danger'>[P] gets reflected by [src]!</span>", \
|
||||
"<span class='userdanger'>[P] gets reflected by [src]!</span>")
|
||||
if(REDIRECT_METHOD_PASSTHROUGH)
|
||||
if(!silent)
|
||||
visible_message("<span class='danger'>[P] passes through [src]!</span>", \
|
||||
"<span class='userdanger'>[P] passes through [src]!</span>")
|
||||
return
|
||||
if(REDIRECT_METHOD_RETURN_TO_SENDER)
|
||||
if(!silent)
|
||||
visible_message("<span class='danger'>[src] deflects [P] back at their attacker!</span>", \
|
||||
"<span class='userdanger'>[src] deflects [P] back at their attacker!</span>")
|
||||
if(P.firer)
|
||||
P.setAngle(Get_Angle(src, P.firer))
|
||||
else
|
||||
P.setAngle(SIMPLIFY_DEGREES(P.Angle + 180))
|
||||
else
|
||||
CRASH("Invalid rediretion mode [redirection_mode]")
|
||||
|
||||
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
|
||||
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
|
||||
if(reflect_bullet_check(P, def_zone))
|
||||
return BULLET_ACT_FORCE_PIERCE // complete projectile permutation
|
||||
if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration))
|
||||
var/list/returnlist = list()
|
||||
var/returned = run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist)
|
||||
if(returned & BLOCK_SHOULD_REDIRECT)
|
||||
handle_projectile_attack_redirection(P, returnlist[BLOCK_RETURN_REDIRECT_METHOD])
|
||||
if(returned & BLOCK_REDIRECTED)
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
if(returned & BLOCK_SUCCESS)
|
||||
P.on_hit(src, 100, def_zone)
|
||||
return BULLET_ACT_BLOCK
|
||||
var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null)
|
||||
@@ -108,12 +104,14 @@
|
||||
return FALSE
|
||||
|
||||
/mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum)
|
||||
// Throwingdatum can be null if someone had an accident() while slipping with an item in hand.
|
||||
var/obj/item/I
|
||||
var/throwpower = 30
|
||||
if(isitem(AM))
|
||||
I = AM
|
||||
throwpower = I.throwforce
|
||||
if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK))
|
||||
var/impacting_zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
|
||||
if(run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone) & BLOCK_SUCCESS)
|
||||
hitpush = FALSE
|
||||
skipcatch = TRUE
|
||||
blocked = TRUE
|
||||
@@ -124,10 +122,9 @@
|
||||
if(I)
|
||||
if(!skipcatch && isturf(I.loc) && catch_item(I))
|
||||
return TRUE
|
||||
var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest
|
||||
var/dtype = BRUTE
|
||||
var/volume = I.get_volume_by_throwforce_and_or_w_class()
|
||||
SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone)
|
||||
SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, impacting_zone)
|
||||
dtype = I.damtype
|
||||
|
||||
if (I.throwforce > 0) //If the weapon's throwforce is greater than zero...
|
||||
@@ -145,8 +142,8 @@
|
||||
if(!blocked)
|
||||
visible_message("<span class='danger'>[src] has been hit by [I].</span>", \
|
||||
"<span class='userdanger'>[src] has been hit by [I].</span>")
|
||||
var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration)
|
||||
apply_damage(I.throwforce, dtype, zone, armor)
|
||||
var/armor = run_armor_check(impacting_zone, "melee", "Your armor has protected your [parse_zone(impacting_zone)].", "Your armor has softened hit to your [parse_zone(impacting_zone)].",I.armour_penetration)
|
||||
apply_damage(I.throwforce, dtype, impacting_zone, armor)
|
||||
if(I.thrownby)
|
||||
log_combat(I.thrownby, src, "threw and hit", I)
|
||||
else
|
||||
@@ -193,17 +190,6 @@
|
||||
if(user == anchored || !isturf(user.loc))
|
||||
return FALSE
|
||||
|
||||
//pacifist vore check.
|
||||
if(user.pulling && HAS_TRAIT(user, TRAIT_PACIFISM) && user.voremode) //they can only do heals, noisy guts, absorbing (technically not harm)
|
||||
if(ismob(user.pulling))
|
||||
var/mob/P = user.pulling
|
||||
if(src != user)
|
||||
to_chat(user, "<span class='notice'>You can't risk digestion!</span>")
|
||||
return FALSE
|
||||
else
|
||||
user.vore_attack(user, P, user)
|
||||
return
|
||||
|
||||
//normal vore check.
|
||||
if(user.pulling && user.grab_state == GRAB_AGGRESSIVE && user.voremode)
|
||||
if(ismob(user.pulling))
|
||||
@@ -283,7 +269,7 @@
|
||||
/mob/living/attack_hand(mob/user)
|
||||
..() //Ignoring parent return value here.
|
||||
SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user)
|
||||
if((user != src) && user.a_intent != INTENT_HELP && check_shields(user, 0, user.name, attack_type = UNARMED_ATTACK))
|
||||
if((user != src) && user.a_intent != INTENT_HELP && (run_block(user, 0, user.name, ATTACK_TYPE_UNARMED | ATTACK_TYPE_MELEE, null, user, check_zone(user.zone_selected)) & BLOCK_SUCCESS))
|
||||
log_combat(user, src, "attempted to touch")
|
||||
visible_message("<span class='warning'>[user] attempted to touch [src]!</span>")
|
||||
return TRUE
|
||||
@@ -294,7 +280,7 @@
|
||||
to_chat(user, "<span class='notice'>You don't want to hurt [src]!</span>")
|
||||
return TRUE
|
||||
var/hulk_verb = pick("smash","pummel")
|
||||
if(user != src && check_shields(user, 15, "the [hulk_verb]ing"))
|
||||
if(user != src && (run_block(user, 15, "the [hulk_verb]ing", ATTACK_TYPE_MELEE, null, user, check_zone(user.zone_selected)) & BLOCK_SUCCESS))
|
||||
return TRUE
|
||||
..()
|
||||
return FALSE
|
||||
@@ -309,14 +295,14 @@
|
||||
M.Feedstop()
|
||||
return // can't attack while eating!
|
||||
|
||||
if(HAS_TRAIT(src, TRAIT_PACIFISM))
|
||||
if(HAS_TRAIT(M, TRAIT_PACIFISM))
|
||||
to_chat(M, "<span class='notice'>You don't want to hurt anyone!</span>")
|
||||
return FALSE
|
||||
|
||||
var/damage = rand(5, 35)
|
||||
if(M.is_adult)
|
||||
damage = rand(20, 40)
|
||||
if(check_shields(M, damage, "the [M.name]"))
|
||||
if(run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS)
|
||||
return FALSE
|
||||
|
||||
if (stat != DEAD)
|
||||
@@ -335,7 +321,7 @@
|
||||
if(HAS_TRAIT(M, TRAIT_PACIFISM))
|
||||
to_chat(M, "<span class='notice'>You don't want to hurt anyone!</span>")
|
||||
return FALSE
|
||||
if(check_shields(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", MELEE_ATTACK, M.armour_penetration))
|
||||
if(run_block(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", ATTACK_TYPE_MELEE, M.armour_penetration, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS)
|
||||
return FALSE
|
||||
if(M.attack_sound)
|
||||
playsound(loc, M.attack_sound, 50, 1, 1)
|
||||
@@ -345,17 +331,15 @@
|
||||
log_combat(M, src, "attacked")
|
||||
return TRUE
|
||||
|
||||
|
||||
/mob/living/attack_paw(mob/living/carbon/monkey/M)
|
||||
if (M.a_intent == INTENT_HARM)
|
||||
if(HAS_TRAIT(M, TRAIT_PACIFISM))
|
||||
to_chat(M, "<span class='notice'>You don't want to hurt anyone!</span>")
|
||||
return FALSE
|
||||
|
||||
if(M.is_muzzled() || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSMOUTH))
|
||||
to_chat(M, "<span class='warning'>You can't bite with your mouth covered!</span>")
|
||||
return FALSE
|
||||
if(check_shields(M, 0, "the [M.name]"))
|
||||
if(run_block(M, 0, "the [M.name]", ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS)
|
||||
return FALSE
|
||||
M.do_attack_animation(src, ATTACK_EFFECT_BITE)
|
||||
if (prob(75))
|
||||
@@ -379,7 +363,7 @@
|
||||
if(HAS_TRAIT(L, TRAIT_PACIFISM))
|
||||
to_chat(L, "<span class='notice'>You don't want to hurt anyone!</span>")
|
||||
return FALSE
|
||||
if(L != src && check_shields(L, rand(1, 3), "the [L.name]"))
|
||||
if(L != src && (run_block(L, rand(1, 3), "the [L.name]", ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED, L, check_zone(L.zone_selected)) & BLOCK_SUCCESS))
|
||||
return FALSE
|
||||
L.do_attack_animation(src)
|
||||
if(prob(90))
|
||||
@@ -393,7 +377,7 @@
|
||||
"<span class='userdanger'>[L.name] has attempted to bite [src]!</span>", null, COMBAT_MESSAGE_RANGE)
|
||||
|
||||
/mob/living/attack_alien(mob/living/carbon/alien/humanoid/M)
|
||||
if((M != src) && M.a_intent != INTENT_HELP && check_shields(M, 0, "the [M.name]"))
|
||||
if((M != src) && M.a_intent != INTENT_HELP && (run_block(M, 0, "the [M.name]", ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS))
|
||||
visible_message("<span class='danger'>[M] attempted to touch [src]!</span>")
|
||||
return FALSE
|
||||
switch(M.a_intent)
|
||||
|
||||
@@ -283,9 +283,7 @@
|
||||
T.visible_message("<span class='warning'>A port on [src] opens to reveal [cable], which promptly falls to the floor.</span>", "<span class='italics'>You hear the soft click of something light and hard falling to the ground.</span>")
|
||||
if("loudness")
|
||||
if(subscreen == 1) // Open Instrument
|
||||
internal_instrument.interact(src)
|
||||
if(subscreen == 2) // Change Instrument type
|
||||
internal_instrument.selectInstrument()
|
||||
internal_instrument.ui_interact(src)
|
||||
|
||||
//updateUsrDialog() We only need to account for the single mob this is intended for, and he will *always* be able to call this window
|
||||
paiInterface() // So we'll just call the update directly rather than doing some default checks
|
||||
|
||||
@@ -112,11 +112,15 @@
|
||||
|
||||
/mob/living/silicon/bullet_act(obj/item/projectile/P, def_zone)
|
||||
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
|
||||
if(reflect_bullet_check(P, def_zone))
|
||||
return -1 // complete projectile permutation
|
||||
if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration))
|
||||
var/list/returnlist = list()
|
||||
var/returned = run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist)
|
||||
if(returned & BLOCK_SHOULD_REDIRECT)
|
||||
handle_projectile_attack_redirection(P, returnlist[BLOCK_RETURN_REDIRECT_METHOD])
|
||||
if(returned & BLOCK_REDIRECTED)
|
||||
return BULLET_ACT_FORCE_PIERCE
|
||||
if(returned & BLOCK_SUCCESS)
|
||||
P.on_hit(src, 100, def_zone)
|
||||
return 2
|
||||
return BULLET_ACT_BLOCK
|
||||
if((P.damage_type == BRUTE || P.damage_type == BURN))
|
||||
adjustBruteLoss(P.damage)
|
||||
if(prob(P.damage*1.5))
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
if (!(istype(src, /mob/living/simple_animal/pet/gondola/gondolapod)))
|
||||
CreateGondola()
|
||||
|
||||
/mob/living/simple_animal/pet/gondola/ComponentInitialize()
|
||||
. = ..()
|
||||
AddElement(/datum/element/spellcasting, SPELL_SKIP_VOCAL) // so they can cast spells despite being silent.
|
||||
|
||||
/mob/living/simple_animal/pet/gondola/proc/CreateGondola()
|
||||
icon_state = null
|
||||
icon_living = null
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
var/blocked = FALSE
|
||||
if(hasmatchingsummoner(hit_atom)) //if the summoner matches don't hurt them
|
||||
blocked = TRUE
|
||||
if(L.check_shields(src, 90, "[name]", attack_type = THROWN_PROJECTILE_ATTACK))
|
||||
if(L.run_block(src, 90, "[name]", ATTACK_TYPE_TACKLE, 0, src) & BLOCK_SUCCESS)
|
||||
blocked = TRUE
|
||||
if(!blocked)
|
||||
L.drop_all_held_items()
|
||||
|
||||
@@ -762,7 +762,7 @@ Difficulty: Very Hard
|
||||
name = "Exit Possession"
|
||||
desc = "Exits the body you are possessing."
|
||||
charge_max = 60
|
||||
clothes_req = 0
|
||||
clothes_req = NONE
|
||||
invocation_type = "none"
|
||||
max_targets = 1
|
||||
range = -1
|
||||
|
||||
@@ -40,27 +40,27 @@
|
||||
deathsound = 'sound/magic/demon_dies.ogg'
|
||||
deathmessage = "begins to shudder as it becomes transparent..."
|
||||
loot_drop = /obj/item/clothing/neck/cloak/herald_cloak
|
||||
|
||||
|
||||
can_talk = 1
|
||||
|
||||
attack_action_types = list(/datum/action/innate/elite_attack/herald_trishot,
|
||||
/datum/action/innate/elite_attack/herald_directionalshot,
|
||||
/datum/action/innate/elite_attack/herald_teleshot,
|
||||
/datum/action/innate/elite_attack/herald_mirror)
|
||||
|
||||
|
||||
var/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/my_mirror = null
|
||||
var/is_mirror = FALSE
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/death()
|
||||
. = ..()
|
||||
if(!is_mirror)
|
||||
addtimer(CALLBACK(src, .proc/become_ghost), 8)
|
||||
if(my_mirror != null)
|
||||
qdel(my_mirror)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/become_ghost()
|
||||
icon_state = "herald_ghost"
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
|
||||
. = ..()
|
||||
if(.)
|
||||
@@ -71,25 +71,25 @@
|
||||
button_icon_state = "herald_trishot"
|
||||
chosen_message = "<span class='boldwarning'>You are now firing three shots in your chosen direction.</span>"
|
||||
chosen_attack_num = HERALD_TRISHOT
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/herald_directionalshot
|
||||
name = "Circular Shot"
|
||||
button_icon_state = "herald_directionalshot"
|
||||
chosen_message = "<span class='boldwarning'>You are firing projectiles in all directions.</span>"
|
||||
chosen_attack_num = HERALD_DIRECTIONALSHOT
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/herald_teleshot
|
||||
name = "Teleport Shot"
|
||||
button_icon_state = "herald_teleshot"
|
||||
chosen_message = "<span class='boldwarning'>You will now fire a shot which teleports you where it lands.</span>"
|
||||
chosen_attack_num = HERALD_TELESHOT
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/herald_mirror
|
||||
name = "Summon Mirror"
|
||||
button_icon_state = "herald_mirror"
|
||||
chosen_message = "<span class='boldwarning'>You will spawn a mirror which duplicates your attacks.</span>"
|
||||
chosen_attack_num = HERALD_MIRROR
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/OpenFire()
|
||||
if(client)
|
||||
switch(chosen_attack)
|
||||
@@ -124,7 +124,7 @@
|
||||
my_mirror.herald_teleshot(target)
|
||||
if(HERALD_MIRROR)
|
||||
herald_mirror()
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/shoot_projectile(turf/marker, set_angle, var/is_teleshot)
|
||||
var/turf/startloc = get_turf(src)
|
||||
var/obj/item/projectile/herald/H = null
|
||||
@@ -137,7 +137,7 @@
|
||||
if(target)
|
||||
H.original = target
|
||||
H.fire(set_angle)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_trishot(target)
|
||||
ranged_cooldown = world.time + 30
|
||||
playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
|
||||
@@ -151,17 +151,17 @@
|
||||
addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 10)
|
||||
addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 12)
|
||||
addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 14)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_circleshot()
|
||||
var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
|
||||
for(var/i in directional_shot_angles)
|
||||
shoot_projectile(get_turf(src), i, FALSE)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/unenrage()
|
||||
if(stat == DEAD || is_mirror)
|
||||
return
|
||||
icon_state = "herald"
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_directionalshot()
|
||||
ranged_cooldown = world.time + 50
|
||||
if(!is_mirror)
|
||||
@@ -172,14 +172,14 @@
|
||||
playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
|
||||
addtimer(CALLBACK(src, .proc/herald_circleshot), 15)
|
||||
addtimer(CALLBACK(src, .proc/unenrage), 20)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_teleshot(target)
|
||||
ranged_cooldown = world.time + 30
|
||||
playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
|
||||
var/target_turf = get_turf(target)
|
||||
var/angle_to_target = Get_Angle(src, target_turf)
|
||||
shoot_projectile(target_turf, angle_to_target, TRUE)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_mirror()
|
||||
ranged_cooldown = world.time + 40
|
||||
playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
|
||||
@@ -190,7 +190,7 @@
|
||||
my_mirror = new_mirror
|
||||
my_mirror.my_master = src
|
||||
my_mirror.faction = faction.Copy()
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror
|
||||
name = "herald's mirror"
|
||||
desc = "This fiendish work of magic copies the herald's attacks. Seems logical to smash it."
|
||||
@@ -203,16 +203,16 @@
|
||||
del_on_death = TRUE
|
||||
is_mirror = TRUE
|
||||
var/mob/living/simple_animal/hostile/asteroid/elite/herald/my_master = null
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Initialize()
|
||||
..()
|
||||
toggle_ai(AI_OFF)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Destroy()
|
||||
if(my_master != null)
|
||||
my_master.my_mirror = null
|
||||
. = ..()
|
||||
|
||||
|
||||
/obj/item/projectile/herald
|
||||
name ="death bolt"
|
||||
icon_state= "chronobolt"
|
||||
@@ -222,7 +222,7 @@
|
||||
eyeblur = 0
|
||||
damage_type = BRUTE
|
||||
pass_flags = PASSTABLE
|
||||
|
||||
|
||||
/obj/item/projectile/herald/teleshot
|
||||
name ="golden bolt"
|
||||
damage = 0
|
||||
@@ -239,11 +239,11 @@
|
||||
var/mob/living/F = firer
|
||||
if(F != null && istype(F, /mob/living/simple_animal/hostile/asteroid/elite) && F.faction_check_mob(L))
|
||||
L.heal_overall_damage(damage)
|
||||
|
||||
|
||||
/obj/item/projectile/herald/teleshot/on_hit(atom/target, blocked = FALSE)
|
||||
. = ..()
|
||||
firer.forceMove(get_turf(src))
|
||||
|
||||
|
||||
//Herald's loot: Cloak of the Prophet
|
||||
|
||||
/obj/item/clothing/neck/cloak/herald_cloak
|
||||
@@ -252,13 +252,13 @@
|
||||
icon = 'icons/obj/lavaland/elite_trophies.dmi'
|
||||
icon_state = "herald_cloak"
|
||||
body_parts_covered = CHEST|GROIN|ARMS
|
||||
hit_reaction_chance = 10
|
||||
|
||||
var/hit_reaction_chance = 10
|
||||
|
||||
/obj/item/clothing/neck/cloak/herald_cloak/proc/reactionshot(mob/living/carbon/owner)
|
||||
var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
|
||||
for(var/i in directional_shot_angles)
|
||||
shoot_projectile(get_turf(owner), i, owner)
|
||||
|
||||
|
||||
/obj/item/clothing/neck/cloak/herald_cloak/proc/shoot_projectile(turf/marker, set_angle, mob/living/carbon/owner)
|
||||
var/turf/startloc = get_turf(owner)
|
||||
var/obj/item/projectile/herald/H = null
|
||||
@@ -266,12 +266,11 @@
|
||||
H.preparePixelProjectile(marker, startloc)
|
||||
H.firer = owner
|
||||
H.fire(set_angle)
|
||||
|
||||
/obj/item/clothing/neck/cloak/herald_cloak/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
|
||||
|
||||
/obj/item/clothing/neck/cloak/herald_cloak/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
|
||||
. = ..()
|
||||
if(rand(1,100) > hit_reaction_chance)
|
||||
return
|
||||
owner.visible_message("<span class='danger'>[owner]'s [src] emits a loud noise as [owner] is struck!</span>")
|
||||
var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315)
|
||||
playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE)
|
||||
addtimer(CALLBACK(src, .proc/reactionshot, owner), 10)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* - Charges at the target after a telegraph, throwing them across the arena should it connect.
|
||||
* - Legionnaire's head detaches, attacking as it's own entity. Has abilities of it's own later into the fight. Once dead, regenerates after a brief period. If the skill is used while the head is off, it will be killed.
|
||||
* - Leaves a pile of bones at your location. Upon using this skill again, you'll swap locations with the bone pile.
|
||||
* - Spews a cloud of smoke from it's maw, wherever said maw is.
|
||||
* - Spews a cloud of smoke from it's maw, wherever said maw is.
|
||||
* A unique fight incorporating the head mechanic of legion into a whole new beast. Combatants will need to make sure the tag-team of head and body don't lure them into a deadly trap.
|
||||
*/
|
||||
|
||||
@@ -45,35 +45,35 @@
|
||||
/datum/action/innate/elite_attack/head_detach,
|
||||
/datum/action/innate/elite_attack/bonfire_teleport,
|
||||
/datum/action/innate/elite_attack/spew_smoke)
|
||||
|
||||
|
||||
var/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/myhead = null
|
||||
var/obj/structure/legionnaire_bonfire/mypile = null
|
||||
var/has_head = TRUE
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/legionnaire_charge
|
||||
name = "Legionnaire Charge"
|
||||
button_icon_state = "legionnaire_charge"
|
||||
chosen_message = "<span class='boldwarning'>You will attempt to grab your opponent and throw them.</span>"
|
||||
chosen_attack_num = LEGIONNAIRE_CHARGE
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/head_detach
|
||||
name = "Release Head"
|
||||
button_icon_state = "head_detach"
|
||||
chosen_message = "<span class='boldwarning'>You will now detach your head or kill it if it is already released.</span>"
|
||||
chosen_attack_num = HEAD_DETACH
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/bonfire_teleport
|
||||
name = "Bonfire Teleport"
|
||||
button_icon_state = "bonfire_teleport"
|
||||
chosen_message = "<span class='boldwarning'>You will leave a bonfire. Second use will let you swap positions with it indefintiely. Using this move on the same tile as your active bonfire removes it.</span>"
|
||||
chosen_attack_num = BONFIRE_TELEPORT
|
||||
|
||||
|
||||
/datum/action/innate/elite_attack/spew_smoke
|
||||
name = "Spew Smoke"
|
||||
button_icon_state = "spew_smoke"
|
||||
chosen_message = "<span class='boldwarning'>Your head will spew smoke in an area, wherever it may be.</span>"
|
||||
chosen_attack_num = SPEW_SMOKE
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/OpenFire()
|
||||
if(client)
|
||||
switch(chosen_attack)
|
||||
@@ -96,7 +96,7 @@
|
||||
bonfire_teleport()
|
||||
if(SPEW_SMOKE)
|
||||
spew_smoke()
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge(target)
|
||||
ranged_cooldown = world.time + 50
|
||||
var/dir_to_target = get_dir(get_turf(src), get_turf(target))
|
||||
@@ -107,7 +107,7 @@
|
||||
playsound(src,'sound/magic/demon_attack1.ogg', 200, 1)
|
||||
visible_message("<span class='boldwarning'>[src] prepares to charge!</span>")
|
||||
addtimer(CALLBACK(src, .proc/legionnaire_charge_2, dir_to_target, 0), 5)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge_2(var/move_dir, var/times_ran)
|
||||
if(times_ran >= 4)
|
||||
return
|
||||
@@ -136,7 +136,7 @@
|
||||
L.Stun(20) //substituting this for the Paralyze from the line above, because we don't have tg paralysis stuff
|
||||
L.adjustBruteLoss(50)
|
||||
addtimer(CALLBACK(src, .proc/legionnaire_charge_2, move_dir, (times_ran + 1)), 2)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/head_detach(target)
|
||||
ranged_cooldown = world.time + 10
|
||||
if(myhead != null)
|
||||
@@ -160,11 +160,11 @@
|
||||
else if(health < maxHealth * 0.5)
|
||||
myhead.melee_damage_lower = 20
|
||||
myhead.melee_damage_upper = 20
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/onHeadDeath()
|
||||
myhead = null
|
||||
addtimer(CALLBACK(src, .proc/regain_head), 50)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/regain_head()
|
||||
has_head = TRUE
|
||||
if(stat == DEAD)
|
||||
@@ -196,7 +196,7 @@
|
||||
forceMove(pileturf)
|
||||
visible_message("<span class='boldwarning'>[src] forms from the bonfire!</span>")
|
||||
mypile.forceMove(legionturf)
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/spew_smoke()
|
||||
ranged_cooldown = world.time + 60
|
||||
var/turf/T = null
|
||||
@@ -213,7 +213,7 @@
|
||||
var/datum/effect_system/smoke_spread/smoke = new
|
||||
smoke.set_up(2, T)
|
||||
smoke.start()
|
||||
|
||||
|
||||
//The legionnaire's head. Basically the same as any legion head, but we have to tell our creator when we die so they can generate another head.
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead
|
||||
name = "legionnaire head"
|
||||
@@ -239,12 +239,12 @@
|
||||
faction = list()
|
||||
ranged = FALSE
|
||||
var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/body = null
|
||||
|
||||
|
||||
/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/death()
|
||||
. = ..()
|
||||
if(body)
|
||||
body.onHeadDeath()
|
||||
|
||||
|
||||
//The legionnaire's bonfire, which can be swapped positions with. Also sets flammable living beings on fire when they walk over it.
|
||||
/obj/structure/legionnaire_bonfire
|
||||
name = "bone pile"
|
||||
@@ -258,20 +258,20 @@
|
||||
light_range = 4
|
||||
light_color = LIGHT_COLOR_RED
|
||||
var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/myowner = null
|
||||
|
||||
|
||||
|
||||
|
||||
/obj/structure/legionnaire_bonfire/Entered(atom/movable/mover, turf/target)
|
||||
if(isliving(mover))
|
||||
var/mob/living/L = mover
|
||||
L.adjust_fire_stacks(3)
|
||||
L.IgniteMob()
|
||||
. = ..()
|
||||
|
||||
|
||||
/obj/structure/legionnaire_bonfire/Destroy()
|
||||
if(myowner != null)
|
||||
myowner.mypile = null
|
||||
. = ..()
|
||||
|
||||
|
||||
//The visual effect which appears in front of legionnaire when he goes to charge.
|
||||
/obj/effect/temp_visual/dragon_swoop/legionnaire
|
||||
duration = 10
|
||||
@@ -280,7 +280,7 @@
|
||||
/obj/effect/temp_visual/dragon_swoop/legionnaire/Initialize()
|
||||
. = ..()
|
||||
transform *= 0.33
|
||||
|
||||
|
||||
// Legionnaire's loot: Legionnaire Spine
|
||||
|
||||
/obj/item/crusher_trophy/legionnaire_spine
|
||||
|
||||
@@ -285,9 +285,10 @@
|
||||
if("Miner")
|
||||
mob_species = pickweight(list(/datum/species/human = 70, /datum/species/lizard = 26, /datum/species/fly = 2, /datum/species/plasmaman = 2))
|
||||
if(mob_species == /datum/species/plasmaman)
|
||||
uniform = /obj/item/clothing/under/plasmaman
|
||||
head = /obj/item/clothing/head/helmet/space/plasmaman
|
||||
uniform = /obj/item/clothing/under/plasmaman/mining
|
||||
head = /obj/item/clothing/head/helmet/space/plasmaman/mining
|
||||
belt = /obj/item/tank/internals/plasmaman/belt
|
||||
mask = /obj/item/clothing/mask/gas/explorer
|
||||
else
|
||||
uniform = /obj/item/clothing/under/rank/cargo/miner/lavaland
|
||||
if (prob(4))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user