mirror of
https://github.com/KabKebab/GS13.git
synced 2026-07-13 17:08:53 +01:00
Merge pull request #10813 from Ghommie/Ghommie-cit540
Unicode support updates from tgstation.
This commit is contained in:
@@ -128,7 +128,7 @@
|
||||
if (smooth & SMOOTH_DIAGONAL)
|
||||
for (var/O in overlays)
|
||||
var/image/I = O
|
||||
if (copytext(I.icon_state, 1, 3) == "d-")
|
||||
if(copytext(I.icon_state, 1, 3) == "d-") //3 == length("d-") + 1
|
||||
return
|
||||
|
||||
var/stuff_on_wall = 0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -407,7 +407,7 @@
|
||||
to_chat(user, "<span class='notice'>You spray a [temp] on \the [target.name]</span>")
|
||||
|
||||
if(length(text_buffer) > 1)
|
||||
text_buffer = copytext(text_buffer,2)
|
||||
text_buffer = copytext(text_buffer, length(text_buffer[1]) + 1)
|
||||
SStgui.update_uis(src)
|
||||
|
||||
if(post_noise)
|
||||
|
||||
+1173
-1173
File diff suppressed because it is too large
Load Diff
@@ -308,9 +308,14 @@ Code:
|
||||
var/list/S = list(" Off","AOff"," On", " AOn")
|
||||
var/list/chg = list("N","C","F")
|
||||
|
||||
//Neither copytext nor copytext_char is appropriate here; neither 30 UTF-8 code units nor 30 code points equates to 30 columns of output.
|
||||
//Some glyphs are very tall or very wide while others are small or even take up no space at all.
|
||||
//Emojis can take modifiers which are many characters but render as only one glyph.
|
||||
//A proper solution here (as far as Unicode goes, maybe not ideal as far as markup goes, a table would be better)
|
||||
//would be to use <span style="width: NNNpx; overflow: none;">[A.area.name]</span>
|
||||
for(var/obj/machinery/power/apc/A in L)
|
||||
menu += copytext(add_tspace(A.area.name, 30), 1, 30)
|
||||
menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_lspace(DisplayPower(A.lastused_total), 6)] [A.cell ? "[add_lspace(round(A.cell.percent()), 3)]% [chg[A.charging+1]]" : " N/C"]<BR>"
|
||||
menu += copytext_char(add_trailing(A.area.name, 30, " "), 1, 30)
|
||||
menu += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_leading(DisplayPower(A.lastused_total), 6, " ")] [A.cell ? "[add_leading(round(A.cell.percent()), 3, " ")]% [chg[A.charging+1]]" : " N/C"]<BR>"
|
||||
|
||||
menu += "</FONT></PRE>"
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
if(pai.radio)
|
||||
pai.radio.wires.cut(wire)
|
||||
if(href_list["setlaws"])
|
||||
var/newlaws = copytext(sanitize(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1]) as message),1,MAX_MESSAGE_LEN)
|
||||
var/newlaws = stripped_multiline_input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", MAX_MESSAGE_LEN)
|
||||
if(newlaws && pai)
|
||||
pai.add_supplied_law(0,newlaws)
|
||||
if(href_list["toggle_holo"])
|
||||
|
||||
@@ -1,146 +1,146 @@
|
||||
/obj/item/electropack
|
||||
name = "electropack"
|
||||
desc = "Dance my monkeys! DANCE!!!"
|
||||
icon = 'icons/obj/radio.dmi'
|
||||
icon_state = "electropack0"
|
||||
item_state = "electropack"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
flags_1 = CONDUCT_1
|
||||
slot_flags = ITEM_SLOT_BACK
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
materials = list(MAT_METAL=10000, MAT_GLASS=2500)
|
||||
|
||||
var/code = 2
|
||||
var/frequency = FREQ_ELECTROPACK
|
||||
var/on = TRUE
|
||||
var/shock_cooldown = FALSE
|
||||
|
||||
/obj/item/electropack/suicide_act(mob/living/carbon/user)
|
||||
user.visible_message("<span class='suicide'>[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
return (FIRELOSS)
|
||||
|
||||
/obj/item/electropack/Initialize()
|
||||
. = ..()
|
||||
set_frequency(frequency)
|
||||
|
||||
/obj/item/electropack/Destroy()
|
||||
SSradio.remove_object(src, frequency)
|
||||
. = ..()
|
||||
|
||||
//ATTACK HAND IGNORING PARENT RETURN VALUE
|
||||
/obj/item/electropack/attack_hand(mob/user)
|
||||
if(iscarbon(user))
|
||||
var/mob/living/carbon/C = user
|
||||
if(src == C.back)
|
||||
to_chat(user, "<span class='warning'>You need help taking this off!</span>")
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/item/electropack/attackby(obj/item/W, mob/living/user, params)
|
||||
if(istype(W, /obj/item/clothing/head/helmet))
|
||||
var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user)
|
||||
A.icon = 'icons/obj/assemblies.dmi'
|
||||
|
||||
if(!user.transferItemToLoc(W, A))
|
||||
to_chat(user, "<span class='warning'>[W] is stuck to your hand, you cannot attach it to [src]!</span>")
|
||||
return
|
||||
W.master = A
|
||||
A.part1 = W
|
||||
|
||||
user.transferItemToLoc(src, A, TRUE)
|
||||
master = A
|
||||
A.part2 = src
|
||||
|
||||
user.put_in_hands(A)
|
||||
A.add_fingerprint(user)
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/item/electropack/Topic(href, href_list)
|
||||
var/mob/living/carbon/C = usr
|
||||
if(usr.stat || usr.restrained() || C.back == src)
|
||||
return
|
||||
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
usr << browse(null, "window=radio")
|
||||
onclose(usr, "radio")
|
||||
return
|
||||
|
||||
if(href_list["set"])
|
||||
if(href_list["set"] == "freq")
|
||||
var/new_freq = input(usr, "Input a new receiving frequency", "Electropack Frequency", format_frequency(frequency)) as num|null
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
return
|
||||
new_freq = unformat_frequency(new_freq)
|
||||
new_freq = sanitize_frequency(new_freq, TRUE)
|
||||
set_frequency(new_freq)
|
||||
|
||||
if(href_list["set"] == "code")
|
||||
var/new_code = input(usr, "Input a new receiving code", "Electropack Code", code) as num|null
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
return
|
||||
new_code = round(new_code)
|
||||
new_code = CLAMP(new_code, 1, 100)
|
||||
code = new_code
|
||||
|
||||
if(href_list["set"] == "power")
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
return
|
||||
on = !(on)
|
||||
icon_state = "electropack[on]"
|
||||
|
||||
if(usr)
|
||||
attack_self(usr)
|
||||
|
||||
return
|
||||
|
||||
/obj/item/electropack/proc/set_frequency(new_frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
frequency = new_frequency
|
||||
SSradio.add_object(src, frequency, RADIO_SIGNALER)
|
||||
return
|
||||
|
||||
/obj/item/electropack/receive_signal(datum/signal/signal)
|
||||
if(!signal || signal.data["code"] != code)
|
||||
return
|
||||
|
||||
if(isliving(loc) && on)
|
||||
if(shock_cooldown == TRUE)
|
||||
return
|
||||
shock_cooldown = TRUE
|
||||
addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100)
|
||||
var/mob/living/L = loc
|
||||
step(L, pick(GLOB.cardinals))
|
||||
|
||||
to_chat(L, "<span class='danger'>You feel a sharp shock!</span>")
|
||||
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
|
||||
s.set_up(3, 1, L)
|
||||
s.start()
|
||||
|
||||
L.Knockdown(100)
|
||||
|
||||
if(master)
|
||||
master.receive_signal()
|
||||
return
|
||||
|
||||
/obj/item/electropack/ui_interact(mob/user)
|
||||
if(!ishuman(user))
|
||||
return
|
||||
|
||||
user.set_machine(src)
|
||||
var/dat = {"
|
||||
<TT>
|
||||
Turned [on ? "On" : "Off"] - <A href='?src=[REF(src)];set=power'>Toggle</A><BR>
|
||||
<B>Frequency/Code</B> for electropack:<BR>
|
||||
Frequency:
|
||||
[format_frequency(src.frequency)]
|
||||
<A href='byond://?src=[REF(src)];set=freq'>Set</A><BR>
|
||||
|
||||
Code:
|
||||
[src.code]
|
||||
<A href='byond://?src=[REF(src)];set=code'>Set</A><BR>
|
||||
</TT>"}
|
||||
user << browse(dat, "window=radio")
|
||||
onclose(user, "radio")
|
||||
return
|
||||
/obj/item/electropack
|
||||
name = "electropack"
|
||||
desc = "Dance my monkeys! DANCE!!!"
|
||||
icon = 'icons/obj/radio.dmi'
|
||||
icon_state = "electropack0"
|
||||
item_state = "electropack"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
flags_1 = CONDUCT_1
|
||||
slot_flags = ITEM_SLOT_BACK
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
materials = list(MAT_METAL=10000, MAT_GLASS=2500)
|
||||
|
||||
var/code = 2
|
||||
var/frequency = FREQ_ELECTROPACK
|
||||
var/on = TRUE
|
||||
var/shock_cooldown = FALSE
|
||||
|
||||
/obj/item/electropack/suicide_act(mob/living/carbon/user)
|
||||
user.visible_message("<span class='suicide'>[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!</span>")
|
||||
return (FIRELOSS)
|
||||
|
||||
/obj/item/electropack/Initialize()
|
||||
. = ..()
|
||||
set_frequency(frequency)
|
||||
|
||||
/obj/item/electropack/Destroy()
|
||||
SSradio.remove_object(src, frequency)
|
||||
. = ..()
|
||||
|
||||
//ATTACK HAND IGNORING PARENT RETURN VALUE
|
||||
/obj/item/electropack/attack_hand(mob/user)
|
||||
if(iscarbon(user))
|
||||
var/mob/living/carbon/C = user
|
||||
if(src == C.back)
|
||||
to_chat(user, "<span class='warning'>You need help taking this off!</span>")
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/item/electropack/attackby(obj/item/W, mob/living/user, params)
|
||||
if(istype(W, /obj/item/clothing/head/helmet))
|
||||
var/obj/item/assembly/shock_kit/A = new /obj/item/assembly/shock_kit(user)
|
||||
A.icon = 'icons/obj/assemblies.dmi'
|
||||
|
||||
if(!user.transferItemToLoc(W, A))
|
||||
to_chat(user, "<span class='warning'>[W] is stuck to your hand, you cannot attach it to [src]!</span>")
|
||||
return
|
||||
W.master = A
|
||||
A.part1 = W
|
||||
|
||||
user.transferItemToLoc(src, A, TRUE)
|
||||
master = A
|
||||
A.part2 = src
|
||||
|
||||
user.put_in_hands(A)
|
||||
A.add_fingerprint(user)
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/item/electropack/Topic(href, href_list)
|
||||
var/mob/living/carbon/C = usr
|
||||
if(usr.stat || usr.restrained() || C.back == src)
|
||||
return
|
||||
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
usr << browse(null, "window=radio")
|
||||
onclose(usr, "radio")
|
||||
return
|
||||
|
||||
if(href_list["set"])
|
||||
if(href_list["set"] == "freq")
|
||||
var/new_freq = input(usr, "Input a new receiving frequency", "Electropack Frequency", format_frequency(frequency)) as num|null
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
return
|
||||
new_freq = unformat_frequency(new_freq)
|
||||
new_freq = sanitize_frequency(new_freq, TRUE)
|
||||
set_frequency(new_freq)
|
||||
|
||||
if(href_list["set"] == "code")
|
||||
var/new_code = input(usr, "Input a new receiving code", "Electropack Code", code) as num|null
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
return
|
||||
new_code = round(new_code)
|
||||
new_code = CLAMP(new_code, 1, 100)
|
||||
code = new_code
|
||||
|
||||
if(href_list["set"] == "power")
|
||||
if(!usr.canUseTopic(src, BE_CLOSE))
|
||||
return
|
||||
on = !(on)
|
||||
icon_state = "electropack[on]"
|
||||
|
||||
if(usr)
|
||||
attack_self(usr)
|
||||
|
||||
return
|
||||
|
||||
/obj/item/electropack/proc/set_frequency(new_frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
frequency = new_frequency
|
||||
SSradio.add_object(src, frequency, RADIO_SIGNALER)
|
||||
return
|
||||
|
||||
/obj/item/electropack/receive_signal(datum/signal/signal)
|
||||
if(!signal || signal.data["code"] != code)
|
||||
return
|
||||
|
||||
if(isliving(loc) && on)
|
||||
if(shock_cooldown == TRUE)
|
||||
return
|
||||
shock_cooldown = TRUE
|
||||
addtimer(VARSET_CALLBACK(src, shock_cooldown, FALSE), 100)
|
||||
var/mob/living/L = loc
|
||||
step(L, pick(GLOB.cardinals))
|
||||
|
||||
to_chat(L, "<span class='danger'>You feel a sharp shock!</span>")
|
||||
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
|
||||
s.set_up(3, 1, L)
|
||||
s.start()
|
||||
|
||||
L.Knockdown(100)
|
||||
|
||||
if(master)
|
||||
master.receive_signal()
|
||||
return
|
||||
|
||||
/obj/item/electropack/ui_interact(mob/user)
|
||||
if(!ishuman(user))
|
||||
return
|
||||
|
||||
user.set_machine(src)
|
||||
var/dat = {"
|
||||
<TT>
|
||||
Turned [on ? "On" : "Off"] - <A href='?src=[REF(src)];set=power'>Toggle</A><BR>
|
||||
<B>Frequency/Code</B> for electropack:<BR>
|
||||
Frequency:
|
||||
[format_frequency(src.frequency)]
|
||||
<A href='byond://?src=[REF(src)];set=freq'>Set</A><BR>
|
||||
|
||||
Code:
|
||||
[src.code]
|
||||
<A href='byond://?src=[REF(src)];set=code'>Set</A><BR>
|
||||
</TT>"}
|
||||
user << browse(dat, "window=radio")
|
||||
onclose(user, "radio")
|
||||
return
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,9 +37,9 @@
|
||||
wielded = 0
|
||||
if(force_unwielded)
|
||||
force = force_unwielded
|
||||
var/sf = findtext(name," (Wielded)")
|
||||
var/sf = findtext(name, " (Wielded)", -10)//10 == length(" (Wielded)")
|
||||
if(sf)
|
||||
name = copytext(name,1,sf)
|
||||
name = copytext(name, 1, sf)
|
||||
else //something wrong
|
||||
name = "[initial(name)]"
|
||||
update_icon()
|
||||
|
||||
+253
-253
@@ -1,253 +1,253 @@
|
||||
|
||||
/obj
|
||||
var/crit_fail = FALSE
|
||||
animate_movement = 2
|
||||
speech_span = SPAN_ROBOT
|
||||
var/obj_flags = CAN_BE_HIT
|
||||
var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT.
|
||||
|
||||
var/damtype = BRUTE
|
||||
var/force = 0
|
||||
|
||||
var/datum/armor/armor
|
||||
var/obj_integrity //defaults to max_integrity
|
||||
var/max_integrity = 500
|
||||
var/integrity_failure = 0 //0 if we have no special broken behavior
|
||||
|
||||
var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
|
||||
|
||||
var/acid_level = 0 //how much acid is on that obj
|
||||
|
||||
var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset.
|
||||
var/current_skin //the item reskin
|
||||
var/list/unique_reskin //List of options to reskin.
|
||||
var/always_reskinnable = FALSE
|
||||
|
||||
// Access levels, used in modules\jobs\access.dm
|
||||
var/list/req_access
|
||||
var/req_access_txt = "0"
|
||||
var/list/req_one_access
|
||||
var/req_one_access_txt = "0"
|
||||
|
||||
var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object
|
||||
|
||||
/obj/vv_edit_var(vname, vval)
|
||||
switch(vname)
|
||||
if("anchored")
|
||||
setAnchored(vval)
|
||||
return TRUE
|
||||
if("obj_flags")
|
||||
if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION))
|
||||
return FALSE
|
||||
if("control_object")
|
||||
var/obj/O = vval
|
||||
if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/obj/Initialize()
|
||||
. = ..()
|
||||
if (islist(armor))
|
||||
armor = getArmor(arglist(armor))
|
||||
else if (!armor)
|
||||
armor = getArmor()
|
||||
else if (!istype(armor, /datum/armor))
|
||||
stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()")
|
||||
|
||||
if(obj_integrity == null)
|
||||
obj_integrity = max_integrity
|
||||
if (set_obj_flags)
|
||||
var/flagslist = splittext(set_obj_flags,";")
|
||||
var/list/string_to_objflag = GLOB.bitfields["obj_flags"]
|
||||
for (var/flag in flagslist)
|
||||
if (findtext(flag,"!",1,2))
|
||||
flag = copytext(flag,1-(length(flag))) // Get all but the initial !
|
||||
obj_flags &= ~string_to_objflag[flag]
|
||||
else
|
||||
obj_flags |= string_to_objflag[flag]
|
||||
if((obj_flags & ON_BLUEPRINTS) && isturf(loc))
|
||||
var/turf/T = loc
|
||||
T.add_blueprints_preround(src)
|
||||
|
||||
|
||||
/obj/Destroy(force=FALSE)
|
||||
if(!ismachinery(src))
|
||||
STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists
|
||||
SStgui.close_uis(src)
|
||||
. = ..()
|
||||
|
||||
/obj/proc/setAnchored(anchorvalue)
|
||||
SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue)
|
||||
anchored = anchorvalue
|
||||
|
||||
/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE)
|
||||
. = ..()
|
||||
if(obj_flags & FROZEN)
|
||||
visible_message("<span class='danger'>[src] shatters into a million pieces!</span>")
|
||||
qdel(src)
|
||||
|
||||
|
||||
/obj/assume_air(datum/gas_mixture/giver)
|
||||
if(loc)
|
||||
return loc.assume_air(giver)
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/remove_air(amount)
|
||||
if(loc)
|
||||
return loc.remove_air(amount)
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/return_air()
|
||||
if(loc)
|
||||
return loc.return_air()
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request)
|
||||
//Return: (NONSTANDARD)
|
||||
// null if object handles breathing logic for lifeform
|
||||
// datum/air_group to tell lifeform to process using that breath return
|
||||
//DEFAULT: Take air from turf to give to have mob process
|
||||
|
||||
if(breath_request>0)
|
||||
var/datum/gas_mixture/environment = return_air()
|
||||
var/breath_percentage = BREATH_VOLUME / environment.return_volume()
|
||||
return remove_air(environment.total_moles() * breath_percentage)
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/proc/updateUsrDialog()
|
||||
if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI))
|
||||
var/is_in_use = FALSE
|
||||
var/list/nearby = viewers(1, src)
|
||||
for(var/mob/M in nearby)
|
||||
if ((M.client && M.machine == src))
|
||||
is_in_use = TRUE
|
||||
ui_interact(M)
|
||||
if(isAI(usr) || iscyborg(usr) || IsAdminGhost(usr))
|
||||
if (!(usr in nearby))
|
||||
if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
|
||||
is_in_use = TRUE
|
||||
ui_interact(usr)
|
||||
|
||||
// check for TK users
|
||||
|
||||
if(ishuman(usr))
|
||||
var/mob/living/carbon/human/H = usr
|
||||
if(!(usr in nearby))
|
||||
if(usr.client && usr.machine==src)
|
||||
if(H.dna.check_mutation(TK))
|
||||
is_in_use = TRUE
|
||||
ui_interact(usr)
|
||||
if (is_in_use)
|
||||
obj_flags |= IN_USE
|
||||
else
|
||||
obj_flags &= ~IN_USE
|
||||
|
||||
/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE)
|
||||
// Check that people are actually using the machine. If not, don't update anymore.
|
||||
if(obj_flags & IN_USE)
|
||||
var/is_in_use = FALSE
|
||||
if(update_viewers)
|
||||
for(var/mob/M in viewers(1, src))
|
||||
if ((M.client && M.machine == src))
|
||||
is_in_use = TRUE
|
||||
src.interact(M)
|
||||
var/ai_in_use = FALSE
|
||||
if(update_ais)
|
||||
ai_in_use = AutoUpdateAI(src)
|
||||
|
||||
if(update_viewers && update_ais) //State change is sure only if we check both
|
||||
if(!ai_in_use && !is_in_use)
|
||||
obj_flags &= ~IN_USE
|
||||
|
||||
|
||||
/obj/attack_ghost(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
ui_interact(user)
|
||||
|
||||
/obj/proc/container_resist(mob/living/user)
|
||||
return
|
||||
|
||||
/obj/proc/update_icon()
|
||||
return
|
||||
|
||||
/mob/proc/unset_machine()
|
||||
if(machine)
|
||||
machine.on_unset_machine(src)
|
||||
machine = null
|
||||
|
||||
//called when the user unsets the machine.
|
||||
/atom/movable/proc/on_unset_machine(mob/user)
|
||||
return
|
||||
|
||||
/mob/proc/set_machine(obj/O)
|
||||
if(src.machine)
|
||||
unset_machine()
|
||||
src.machine = O
|
||||
if(istype(O))
|
||||
O.obj_flags |= IN_USE
|
||||
|
||||
/obj/item/proc/updateSelfDialog()
|
||||
var/mob/M = src.loc
|
||||
if(istype(M) && M.client && M.machine == src)
|
||||
src.attack_self(M)
|
||||
|
||||
/obj/proc/hide(h)
|
||||
return
|
||||
|
||||
/obj/singularity_pull(S, current_size)
|
||||
..()
|
||||
if(!anchored || current_size >= STAGE_FIVE)
|
||||
step_towards(src,S)
|
||||
|
||||
/obj/get_dumping_location(datum/component/storage/source,mob/user)
|
||||
return get_turf(src)
|
||||
|
||||
/obj/proc/CanAStarPass()
|
||||
. = !density
|
||||
|
||||
/obj/proc/check_uplink_validity()
|
||||
return 1
|
||||
|
||||
/obj/proc/intercept_user_move(dir, mob, newLoc, oldLoc)
|
||||
return
|
||||
|
||||
/obj/vv_get_dropdown()
|
||||
. = ..()
|
||||
.["Delete all of type"] = "?_src_=vars;[HrefToken()];delall=[REF(src)]"
|
||||
.["Osay"] = "?_src_=vars;[HrefToken()];osay[REF(src)]"
|
||||
.["Modify armor values"] = "?_src_=vars;[HrefToken()];modarmor=[REF(src)]"
|
||||
|
||||
/obj/examine(mob/user)
|
||||
. = ..()
|
||||
if(obj_flags & UNIQUE_RENAME)
|
||||
. += "<span class='notice'>Use a pen on it to rename it or change its description.</span>"
|
||||
if(unique_reskin && (!current_skin || always_reskinnable))
|
||||
. += "<span class='notice'>Alt-click it to reskin it.</span>"
|
||||
|
||||
/obj/AltClick(mob/user)
|
||||
. = ..()
|
||||
if(unique_reskin && (!current_skin || always_reskinnable) && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
|
||||
reskin_obj(user)
|
||||
return TRUE
|
||||
|
||||
/obj/proc/reskin_obj(mob/M)
|
||||
if(!LAZYLEN(unique_reskin))
|
||||
return
|
||||
var/dat = "<b>Reskin options for [name]:</b>\n"
|
||||
for(var/V in unique_reskin)
|
||||
var/output = icon2html(src, M, unique_reskin[V])
|
||||
dat += "[V]: <span class='reallybig'>[output]</span>\n"
|
||||
to_chat(M, dat)
|
||||
|
||||
var/choice = input(M, always_reskinnable ? "Choose the a reskin for [src]" : "Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin
|
||||
if(QDELETED(src) || !choice || (current_skin && !always_reskinnable) || M.incapacitated() || !in_range(M,src) || !unique_reskin[choice] || unique_reskin[choice] == current_skin)
|
||||
return
|
||||
current_skin = choice
|
||||
icon_state = unique_reskin[choice]
|
||||
to_chat(M, "[src] is now skinned as '[choice]'.")
|
||||
|
||||
/obj
|
||||
var/crit_fail = FALSE
|
||||
animate_movement = 2
|
||||
speech_span = SPAN_ROBOT
|
||||
var/obj_flags = CAN_BE_HIT
|
||||
var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT.
|
||||
|
||||
var/damtype = BRUTE
|
||||
var/force = 0
|
||||
|
||||
var/datum/armor/armor
|
||||
var/obj_integrity //defaults to max_integrity
|
||||
var/max_integrity = 500
|
||||
var/integrity_failure = 0 //0 if we have no special broken behavior
|
||||
|
||||
var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF
|
||||
|
||||
var/acid_level = 0 //how much acid is on that obj
|
||||
|
||||
var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset.
|
||||
var/current_skin //the item reskin
|
||||
var/list/unique_reskin //List of options to reskin.
|
||||
var/always_reskinnable = FALSE
|
||||
|
||||
// Access levels, used in modules\jobs\access.dm
|
||||
var/list/req_access
|
||||
var/req_access_txt = "0"
|
||||
var/list/req_one_access
|
||||
var/req_one_access_txt = "0"
|
||||
|
||||
var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object
|
||||
|
||||
/obj/vv_edit_var(vname, vval)
|
||||
switch(vname)
|
||||
if("anchored")
|
||||
setAnchored(vval)
|
||||
return TRUE
|
||||
if("obj_flags")
|
||||
if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION))
|
||||
return FALSE
|
||||
if("control_object")
|
||||
var/obj/O = vval
|
||||
if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/obj/Initialize()
|
||||
. = ..()
|
||||
if (islist(armor))
|
||||
armor = getArmor(arglist(armor))
|
||||
else if (!armor)
|
||||
armor = getArmor()
|
||||
else if (!istype(armor, /datum/armor))
|
||||
stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()")
|
||||
|
||||
if(obj_integrity == null)
|
||||
obj_integrity = max_integrity
|
||||
if (set_obj_flags)
|
||||
var/flagslist = splittext(set_obj_flags,";")
|
||||
var/list/string_to_objflag = GLOB.bitfields["obj_flags"]
|
||||
for (var/flag in flagslist)
|
||||
if(flag[1] == "!")
|
||||
flag = copytext(flag, length(flag[1]) + 1) // Get all but the initial !
|
||||
obj_flags &= ~string_to_objflag[flag]
|
||||
else
|
||||
obj_flags |= string_to_objflag[flag]
|
||||
if((obj_flags & ON_BLUEPRINTS) && isturf(loc))
|
||||
var/turf/T = loc
|
||||
T.add_blueprints_preround(src)
|
||||
|
||||
|
||||
/obj/Destroy(force=FALSE)
|
||||
if(!ismachinery(src))
|
||||
STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists
|
||||
SStgui.close_uis(src)
|
||||
. = ..()
|
||||
|
||||
/obj/proc/setAnchored(anchorvalue)
|
||||
SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue)
|
||||
anchored = anchorvalue
|
||||
|
||||
/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE)
|
||||
. = ..()
|
||||
if(obj_flags & FROZEN)
|
||||
visible_message("<span class='danger'>[src] shatters into a million pieces!</span>")
|
||||
qdel(src)
|
||||
|
||||
|
||||
/obj/assume_air(datum/gas_mixture/giver)
|
||||
if(loc)
|
||||
return loc.assume_air(giver)
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/remove_air(amount)
|
||||
if(loc)
|
||||
return loc.remove_air(amount)
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/return_air()
|
||||
if(loc)
|
||||
return loc.return_air()
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request)
|
||||
//Return: (NONSTANDARD)
|
||||
// null if object handles breathing logic for lifeform
|
||||
// datum/air_group to tell lifeform to process using that breath return
|
||||
//DEFAULT: Take air from turf to give to have mob process
|
||||
|
||||
if(breath_request>0)
|
||||
var/datum/gas_mixture/environment = return_air()
|
||||
var/breath_percentage = BREATH_VOLUME / environment.return_volume()
|
||||
return remove_air(environment.total_moles() * breath_percentage)
|
||||
else
|
||||
return null
|
||||
|
||||
/obj/proc/updateUsrDialog()
|
||||
if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI))
|
||||
var/is_in_use = FALSE
|
||||
var/list/nearby = viewers(1, src)
|
||||
for(var/mob/M in nearby)
|
||||
if ((M.client && M.machine == src))
|
||||
is_in_use = TRUE
|
||||
ui_interact(M)
|
||||
if(isAI(usr) || iscyborg(usr) || IsAdminGhost(usr))
|
||||
if (!(usr in nearby))
|
||||
if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
|
||||
is_in_use = TRUE
|
||||
ui_interact(usr)
|
||||
|
||||
// check for TK users
|
||||
|
||||
if(ishuman(usr))
|
||||
var/mob/living/carbon/human/H = usr
|
||||
if(!(usr in nearby))
|
||||
if(usr.client && usr.machine==src)
|
||||
if(H.dna.check_mutation(TK))
|
||||
is_in_use = TRUE
|
||||
ui_interact(usr)
|
||||
if (is_in_use)
|
||||
obj_flags |= IN_USE
|
||||
else
|
||||
obj_flags &= ~IN_USE
|
||||
|
||||
/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE)
|
||||
// Check that people are actually using the machine. If not, don't update anymore.
|
||||
if(obj_flags & IN_USE)
|
||||
var/is_in_use = FALSE
|
||||
if(update_viewers)
|
||||
for(var/mob/M in viewers(1, src))
|
||||
if ((M.client && M.machine == src))
|
||||
is_in_use = TRUE
|
||||
src.interact(M)
|
||||
var/ai_in_use = FALSE
|
||||
if(update_ais)
|
||||
ai_in_use = AutoUpdateAI(src)
|
||||
|
||||
if(update_viewers && update_ais) //State change is sure only if we check both
|
||||
if(!ai_in_use && !is_in_use)
|
||||
obj_flags &= ~IN_USE
|
||||
|
||||
|
||||
/obj/attack_ghost(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
ui_interact(user)
|
||||
|
||||
/obj/proc/container_resist(mob/living/user)
|
||||
return
|
||||
|
||||
/obj/proc/update_icon()
|
||||
return
|
||||
|
||||
/mob/proc/unset_machine()
|
||||
if(machine)
|
||||
machine.on_unset_machine(src)
|
||||
machine = null
|
||||
|
||||
//called when the user unsets the machine.
|
||||
/atom/movable/proc/on_unset_machine(mob/user)
|
||||
return
|
||||
|
||||
/mob/proc/set_machine(obj/O)
|
||||
if(src.machine)
|
||||
unset_machine()
|
||||
src.machine = O
|
||||
if(istype(O))
|
||||
O.obj_flags |= IN_USE
|
||||
|
||||
/obj/item/proc/updateSelfDialog()
|
||||
var/mob/M = src.loc
|
||||
if(istype(M) && M.client && M.machine == src)
|
||||
src.attack_self(M)
|
||||
|
||||
/obj/proc/hide(h)
|
||||
return
|
||||
|
||||
/obj/singularity_pull(S, current_size)
|
||||
..()
|
||||
if(!anchored || current_size >= STAGE_FIVE)
|
||||
step_towards(src,S)
|
||||
|
||||
/obj/get_dumping_location(datum/component/storage/source,mob/user)
|
||||
return get_turf(src)
|
||||
|
||||
/obj/proc/CanAStarPass()
|
||||
. = !density
|
||||
|
||||
/obj/proc/check_uplink_validity()
|
||||
return 1
|
||||
|
||||
/obj/proc/intercept_user_move(dir, mob, newLoc, oldLoc)
|
||||
return
|
||||
|
||||
/obj/vv_get_dropdown()
|
||||
. = ..()
|
||||
.["Delete all of type"] = "?_src_=vars;[HrefToken()];delall=[REF(src)]"
|
||||
.["Osay"] = "?_src_=vars;[HrefToken()];osay[REF(src)]"
|
||||
.["Modify armor values"] = "?_src_=vars;[HrefToken()];modarmor=[REF(src)]"
|
||||
|
||||
/obj/examine(mob/user)
|
||||
. = ..()
|
||||
if(obj_flags & UNIQUE_RENAME)
|
||||
. += "<span class='notice'>Use a pen on it to rename it or change its description.</span>"
|
||||
if(unique_reskin && (!current_skin || always_reskinnable))
|
||||
. += "<span class='notice'>Alt-click it to reskin it.</span>"
|
||||
|
||||
/obj/AltClick(mob/user)
|
||||
. = ..()
|
||||
if(unique_reskin && (!current_skin || always_reskinnable) && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY))
|
||||
reskin_obj(user)
|
||||
return TRUE
|
||||
|
||||
/obj/proc/reskin_obj(mob/M)
|
||||
if(!LAZYLEN(unique_reskin))
|
||||
return
|
||||
var/dat = "<b>Reskin options for [name]:</b>\n"
|
||||
for(var/V in unique_reskin)
|
||||
var/output = icon2html(src, M, unique_reskin[V])
|
||||
dat += "[V]: <span class='reallybig'>[output]</span>\n"
|
||||
to_chat(M, dat)
|
||||
|
||||
var/choice = input(M, always_reskinnable ? "Choose the a reskin for [src]" : "Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin
|
||||
if(QDELETED(src) || !choice || (current_skin && !always_reskinnable) || M.incapacitated() || !in_range(M,src) || !unique_reskin[choice] || unique_reskin[choice] == current_skin)
|
||||
return
|
||||
current_skin = choice
|
||||
icon_state = unique_reskin[choice]
|
||||
to_chat(M, "[src] is now skinned as '[choice]'.")
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
|
||||
switch(choice)
|
||||
if("name")
|
||||
var/newname = copytext(sanitize(input(H, "Who are we again?", "Name change", H.name) as null|text),1,MAX_NAME_LEN)
|
||||
var/newname = reject_bad_name(stripped_input(H, "Who are we again?", "Name change", H.name, MAX_NAME_LEN))
|
||||
|
||||
if(!newname)
|
||||
return
|
||||
|
||||
@@ -1,381 +1,382 @@
|
||||
|
||||
#define MUSICIAN_HEARCHECK_MINDELAY 4
|
||||
#define MUSIC_MAXLINES 600
|
||||
#define MUSIC_MAXLINECHARS 50
|
||||
|
||||
/datum/song
|
||||
var/name = "Untitled"
|
||||
var/list/lines = new()
|
||||
var/tempo = 5 // delay between notes
|
||||
|
||||
var/playing = 0 // if we're playing
|
||||
var/help = 0 // if help is open
|
||||
var/edit = 1 // if we're in editing mode
|
||||
var/repeat = 0 // number of times remaining to repeat
|
||||
var/max_repeats = 10 // maximum times we can repeat
|
||||
|
||||
var/instrumentDir = "piano" // the folder with the sounds
|
||||
var/instrumentExt = "ogg" // the file extension
|
||||
var/obj/instrumentObj = null // the associated obj playing the sound
|
||||
var/last_hearcheck = 0
|
||||
var/list/hearing_mobs
|
||||
|
||||
/datum/song/New(dir, obj, ext = "ogg")
|
||||
tempo = sanitize_tempo(tempo)
|
||||
instrumentDir = dir
|
||||
instrumentObj = obj
|
||||
instrumentExt = ext
|
||||
|
||||
/datum/song/Destroy()
|
||||
instrumentObj = null
|
||||
return ..()
|
||||
|
||||
// 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(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/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]"
|
||||
soundfile = file(soundfile)
|
||||
// make sure the note exists
|
||||
if(!fexists(soundfile))
|
||||
return
|
||||
// and play
|
||||
var/turf/source = get_turf(instrumentObj)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
LAZYCLEARLIST(hearing_mobs)
|
||||
for(var/mob/M in get_hearers_in_view(15, source))
|
||||
if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
LAZYADD(hearing_mobs, M)
|
||||
last_hearcheck = world.time
|
||||
|
||||
var/sound/music_played = sound(soundfile)
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.playsound_local(source, null, 100, falloff = 5, S = music_played)
|
||||
|
||||
/datum/song/proc/updateDialog(mob/user)
|
||||
instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise
|
||||
|
||||
/datum/song/proc/shouldStopPlaying(mob/user)
|
||||
if(instrumentObj)
|
||||
if(!user.canUseTopic(instrumentObj))
|
||||
return TRUE
|
||||
return !instrumentObj.anchored // add special cases to stop in subclasses
|
||||
else
|
||||
return TRUE
|
||||
|
||||
/datum/song/proc/playsong(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), ","))
|
||||
var/list/notes = splittext(beat, "/")
|
||||
for(var/note in splittext(notes[1], "-"))
|
||||
if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case
|
||||
playing = FALSE
|
||||
hearing_mobs = null
|
||||
return
|
||||
if(!length(note))
|
||||
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)
|
||||
if(user.dizziness > 0 && prob(user.dizziness / 2))
|
||||
cur_note = CLAMP(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7)
|
||||
if(user.dizziness > 0 && prob(user.dizziness / 5))
|
||||
if(prob(30))
|
||||
cur_acc[cur_note] = "#"
|
||||
else if(prob(42))
|
||||
cur_acc[cur_note] = "b"
|
||||
else if(prob(75))
|
||||
cur_acc[cur_note] = "n"
|
||||
playnote(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)
|
||||
repeat--
|
||||
hearing_mobs = null
|
||||
playing = FALSE
|
||||
repeat = 0
|
||||
updateDialog(user)
|
||||
|
||||
/datum/song/proc/interact(mob/user)
|
||||
var/dat = ""
|
||||
|
||||
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(!edit)
|
||||
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", instrumentObj.name, 700, 500)
|
||||
popup.set_content(dat)
|
||||
popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state))
|
||||
popup.open()
|
||||
|
||||
/datum/song/proc/ParseSong(text)
|
||||
set waitfor = FALSE
|
||||
//split into lines
|
||||
lines = splittext(text, "\n")
|
||||
if(lines.len)
|
||||
if(copytext(lines[1],1,6) == "BPM: ")
|
||||
tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6)))
|
||||
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(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(instrumentObj))
|
||||
usr << browse(null, "window=instrument")
|
||||
usr.unset_machine()
|
||||
return
|
||||
|
||||
instrumentObj.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(instrumentObj, usr))
|
||||
return
|
||||
|
||||
if(length(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(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
||||
ParseSong(t)
|
||||
|
||||
else if(href_list["help"])
|
||||
help = text2num(href_list["help"]) - 1
|
||||
|
||||
else if(href_list["edit"])
|
||||
edit = 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"])
|
||||
playing = TRUE
|
||||
spawn()
|
||||
playsong(usr)
|
||||
|
||||
else if(href_list["newline"])
|
||||
var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null)
|
||||
if(!newline || !in_range(instrumentObj, 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 = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null)
|
||||
if(!content || !in_range(instrumentObj, usr))
|
||||
return
|
||||
if(length(content) > MUSIC_MAXLINECHARS)
|
||||
content = copytext(content, 1, MUSIC_MAXLINECHARS)
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines[num] = content
|
||||
|
||||
else if(href_list["stop"])
|
||||
playing = FALSE
|
||||
hearing_mobs = null
|
||||
|
||||
updateDialog(usr)
|
||||
return
|
||||
|
||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||
new_tempo = abs(new_tempo)
|
||||
return max(round(new_tempo, world.tick_lag), world.tick_lag)
|
||||
|
||||
// subclass for handheld instruments, like violin
|
||||
/datum/song/handheld
|
||||
|
||||
/datum/song/handheld/updateDialog(mob/user)
|
||||
instrumentObj.interact(user)
|
||||
|
||||
/datum/song/handheld/shouldStopPlaying()
|
||||
if(instrumentObj)
|
||||
return !isliving(instrumentObj.loc)
|
||||
else
|
||||
return TRUE
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/obj/structure/piano
|
||||
name = "space minimoog"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "minimoog"
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
var/datum/song/song
|
||||
|
||||
/obj/structure/piano/unanchored
|
||||
anchored = FALSE
|
||||
|
||||
/obj/structure/piano/New()
|
||||
..()
|
||||
song = new("piano", src)
|
||||
|
||||
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"
|
||||
|
||||
/obj/structure/piano/Destroy()
|
||||
qdel(song)
|
||||
song = null
|
||||
return ..()
|
||||
|
||||
/obj/structure/piano/Initialize(mapload)
|
||||
. = ..()
|
||||
if(mapload)
|
||||
song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
|
||||
|
||||
/obj/structure/piano/attack_hand(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
interact(user)
|
||||
|
||||
/obj/structure/piano/attack_paw(mob/user)
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/structure/piano/interact(mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/structure/piano/ui_interact(mob/user)
|
||||
if(!user || !anchored)
|
||||
return
|
||||
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return 1
|
||||
user.set_machine(src)
|
||||
song.interact(user)
|
||||
|
||||
/obj/structure/piano/wrench_act(mob/living/user, obj/item/I)
|
||||
default_unfasten_wrench(user, I, 40)
|
||||
return TRUE
|
||||
|
||||
#define MUSICIAN_HEARCHECK_MINDELAY 4
|
||||
#define MUSIC_MAXLINES 600
|
||||
#define MUSIC_MAXLINECHARS 50
|
||||
|
||||
/datum/song
|
||||
var/name = "Untitled"
|
||||
var/list/lines = new()
|
||||
var/tempo = 5 // delay between notes
|
||||
|
||||
var/playing = 0 // if we're playing
|
||||
var/help = 0 // if help is open
|
||||
var/edit = 1 // if we're in editing mode
|
||||
var/repeat = 0 // number of times remaining to repeat
|
||||
var/max_repeats = 10 // maximum times we can repeat
|
||||
|
||||
var/instrumentDir = "piano" // the folder with the sounds
|
||||
var/instrumentExt = "ogg" // the file extension
|
||||
var/obj/instrumentObj = null // the associated obj playing the sound
|
||||
var/last_hearcheck = 0
|
||||
var/list/hearing_mobs
|
||||
|
||||
/datum/song/New(dir, obj, ext = "ogg")
|
||||
tempo = sanitize_tempo(tempo)
|
||||
instrumentDir = dir
|
||||
instrumentObj = obj
|
||||
instrumentExt = ext
|
||||
|
||||
/datum/song/Destroy()
|
||||
instrumentObj = null
|
||||
return ..()
|
||||
|
||||
// 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(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/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]"
|
||||
soundfile = file(soundfile)
|
||||
// make sure the note exists
|
||||
if(!fexists(soundfile))
|
||||
return
|
||||
// and play
|
||||
var/turf/source = get_turf(instrumentObj)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
LAZYCLEARLIST(hearing_mobs)
|
||||
for(var/mob/M in get_hearers_in_view(15, source))
|
||||
if(!M.client || !(M.client.prefs.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
LAZYADD(hearing_mobs, M)
|
||||
last_hearcheck = world.time
|
||||
|
||||
var/sound/music_played = sound(soundfile)
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.playsound_local(source, null, 100, falloff = 5, S = music_played)
|
||||
|
||||
/datum/song/proc/updateDialog(mob/user)
|
||||
instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise
|
||||
|
||||
/datum/song/proc/shouldStopPlaying(mob/user)
|
||||
if(instrumentObj)
|
||||
if(!user.canUseTopic(instrumentObj))
|
||||
return TRUE
|
||||
return !instrumentObj.anchored // add special cases to stop in subclasses
|
||||
else
|
||||
return TRUE
|
||||
|
||||
/datum/song/proc/playsong(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), ","))
|
||||
var/list/notes = splittext(beat, "/")
|
||||
for(var/note in splittext(notes[1], "-"))
|
||||
if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case
|
||||
playing = FALSE
|
||||
hearing_mobs = null
|
||||
return
|
||||
if(!length(note))
|
||||
continue
|
||||
var/cur_note = text2ascii(note) - 96
|
||||
if(cur_note < 1 || cur_note > 7)
|
||||
continue
|
||||
var/notelen = length(note)
|
||||
var/ni = ""
|
||||
for(var/i = length(note[1]) + 1, i <= notelen, i += length(ni))
|
||||
ni = note[i]
|
||||
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)
|
||||
if(user.dizziness > 0 && prob(user.dizziness / 2))
|
||||
cur_note = CLAMP(cur_note + rand(round(-user.dizziness / 10), round(user.dizziness / 10)), 1, 7)
|
||||
if(user.dizziness > 0 && prob(user.dizziness / 5))
|
||||
if(prob(30))
|
||||
cur_acc[cur_note] = "#"
|
||||
else if(prob(42))
|
||||
cur_acc[cur_note] = "b"
|
||||
else if(prob(75))
|
||||
cur_acc[cur_note] = "n"
|
||||
playnote(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)
|
||||
repeat--
|
||||
hearing_mobs = null
|
||||
playing = FALSE
|
||||
repeat = 0
|
||||
updateDialog(user)
|
||||
|
||||
/datum/song/proc/interact(mob/user)
|
||||
var/dat = ""
|
||||
|
||||
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(!edit)
|
||||
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", instrumentObj.name, 700, 500)
|
||||
popup.set_content(dat)
|
||||
popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.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))
|
||||
tempo = sanitize_tempo(600 / text2num(copytext(lines[1], length(bpm_string) + 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(instrumentObj))
|
||||
usr << browse(null, "window=instrument")
|
||||
usr.unset_machine()
|
||||
return
|
||||
|
||||
instrumentObj.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(instrumentObj, 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"])
|
||||
edit = 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"])
|
||||
playing = TRUE
|
||||
spawn()
|
||||
playsong(usr)
|
||||
|
||||
else if(href_list["newline"])
|
||||
var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null)
|
||||
if(!newline || !in_range(instrumentObj, 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: ", instrumentObj.name, lines[num], MUSIC_MAXLINECHARS)
|
||||
if(!content || !in_range(instrumentObj, usr))
|
||||
return
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines[num] = content
|
||||
|
||||
else if(href_list["stop"])
|
||||
playing = FALSE
|
||||
hearing_mobs = null
|
||||
|
||||
updateDialog(usr)
|
||||
return
|
||||
|
||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||
new_tempo = abs(new_tempo)
|
||||
return max(round(new_tempo, world.tick_lag), world.tick_lag)
|
||||
|
||||
// subclass for handheld instruments, like violin
|
||||
/datum/song/handheld
|
||||
|
||||
/datum/song/handheld/updateDialog(mob/user)
|
||||
instrumentObj.interact(user)
|
||||
|
||||
/datum/song/handheld/shouldStopPlaying()
|
||||
if(instrumentObj)
|
||||
return !isliving(instrumentObj.loc)
|
||||
else
|
||||
return TRUE
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/obj/structure/piano
|
||||
name = "space minimoog"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "minimoog"
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
var/datum/song/song
|
||||
|
||||
/obj/structure/piano/unanchored
|
||||
anchored = FALSE
|
||||
|
||||
/obj/structure/piano/New()
|
||||
..()
|
||||
song = new("piano", src)
|
||||
|
||||
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"
|
||||
|
||||
/obj/structure/piano/Destroy()
|
||||
qdel(song)
|
||||
song = null
|
||||
return ..()
|
||||
|
||||
/obj/structure/piano/Initialize(mapload)
|
||||
. = ..()
|
||||
if(mapload)
|
||||
song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
|
||||
|
||||
/obj/structure/piano/attack_hand(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
interact(user)
|
||||
|
||||
/obj/structure/piano/attack_paw(mob/user)
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/structure/piano/interact(mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/structure/piano/ui_interact(mob/user)
|
||||
if(!user || !anchored)
|
||||
return
|
||||
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return 1
|
||||
user.set_machine(src)
|
||||
song.interact(user)
|
||||
|
||||
/obj/structure/piano/wrench_act(mob/living/user, obj/item/I)
|
||||
default_unfasten_wrench(user, I, 40)
|
||||
return TRUE
|
||||
|
||||
Reference in New Issue
Block a user