Merge remote-tracking branch 'origin/master' into perlin-genny

This commit is contained in:
LetterN
2021-03-20 20:36:56 +08:00
267 changed files with 109865 additions and 107683 deletions
+1
View File
@@ -331,6 +331,7 @@
return
. = anchored
anchored = anchorvalue
SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue)
// SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue)
/atom/movable/proc/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
@@ -24,6 +24,7 @@
traitor_name = "Bloodsucker"
antag_flag = ROLE_BLOODSUCKER
false_report_weight = 1
chaos = 4
restricted_jobs = list("AI","Cyborg")
protected_jobs = list("Chaplain", "Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20
@@ -87,7 +88,7 @@
// Init Sunlight (called from datum_bloodsucker.on_gain(), in case game mode isn't even Bloodsucker
/datum/game_mode/proc/check_start_sunlight()
// Already Sunlight (and not about to cancel)
if(istype(bloodsucker_sunlight) && !bloodsucker_sunlight.cancel_me)
if(istype(bloodsucker_sunlight))
return
bloodsucker_sunlight = new ()
@@ -97,7 +98,6 @@
if(!istype(bloodsucker_sunlight))
return
if(bloodsuckers.len <= 0)
bloodsucker_sunlight.cancel_me = TRUE
qdel(bloodsucker_sunlight)
bloodsucker_sunlight = null
@@ -6,6 +6,7 @@
name = "traitor+brothers"
config_tag = "traitorbro"
required_players = 25
chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
@@ -10,6 +10,7 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th
config_tag = "changeling"
antag_flag = ROLE_CHANGELING
false_report_weight = 10
chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to ling role blacklist
required_players = 15
@@ -2,6 +2,7 @@
name = "traitor+changeling"
config_tag = "traitorchan"
false_report_weight = 10
chaos = 6
traitors_possible = 3 //hard limit on traitors if scaling is turned off
restricted_jobs = list("AI", "Cyborg")
required_players = 25
@@ -134,6 +134,7 @@ Credit where due:
config_tag = "clockwork_cult"
antag_flag = ROLE_SERVANT_OF_RATVAR
false_report_weight = 10
chaos = 8
required_players = 24 //Fixing this directly for now since apparently config machine for forcing modes broke.
required_enemies = 3
recommended_enemies = 5
+1 -1
View File
@@ -1,7 +1,7 @@
/datum/game_mode/nuclear/clown_ops
name = "clown ops"
config_tag = "clownops"
chaos = 8
announce_span = "danger"
announce_text = "Clown empire forces are approaching the station in an attempt to HONK it!\n\
<span class='danger'>Operatives</span>: Secure the nuclear authentication disk and use your bananium fission explosive to HONK the station.\n\
+1
View File
@@ -38,6 +38,7 @@
config_tag = "cult"
antag_flag = ROLE_CULTIST
false_report_weight = 10
chaos = 8
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 30
@@ -1,6 +1,7 @@
/datum/game_mode/devil/devil_agents
name = "Devil Agents"
config_tag = "devil_agents"
chaos = 5
required_players = 25
required_enemies = 3
recommended_enemies = 8
@@ -3,6 +3,7 @@
config_tag = "devil"
antag_flag = ROLE_DEVIL
false_report_weight = 1
chaos = 3
protected_jobs = list("Lawyer", "Curator", "Chaplain", "Head of Security", "Captain", "AI")
required_players = 0
required_enemies = 1
@@ -3,6 +3,7 @@
config_tag = "heresy"
antag_flag = ROLE_HERETIC
false_report_weight = 5
chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster") //citadel change - adds HoP, CE, CMO, and RD to heretic role blacklist
required_players = 15
+1
View File
@@ -3,6 +3,7 @@
config_tag = "secret_extended"
false_report_weight = 5
required_players = 0
chaos = 0
announce_span = "notice"
announce_text = "Just have fun and enjoy the game!"
+9 -1
View File
@@ -17,6 +17,7 @@
var/config_tag = null
var/votable = 1
var/probability = 0
var/chaos = 5 // 0-9, used for weighting round-to-round
var/false_report_weight = 0 //How often will this show up incorrectly in a centcom report?
var/station_was_nuked = 0 //see nuclearbomb.dm and malfunction.dm
var/nuke_off_station = 0 //Used for tracking where the nuke hit
@@ -419,7 +420,7 @@
for(var/mob/dead/new_player/player in players)
if(player.client && player.ready == PLAYER_READY_TO_PLAY)
if(role in player.client.prefs.be_special)
if((role in player.client.prefs.be_special) && (ROLE_SYNDICATE in player.client.prefs.be_special))
if(!jobban_isbanned(player, ROLE_SYNDICATE) && !QDELETED(player) && !jobban_isbanned(player, role) && !QDELETED(player)) //Nodrak/Carn: Antag Job-bans
if(age_check(player.client)) //Must be older than the minimum age
candidates += player.mind // Get a list of all the people who want to be the antagonist for this round
@@ -623,3 +624,10 @@
/// Mode specific info for ghost game_info
/datum/game_mode/proc/ghost_info()
return
/datum/game_mode/proc/get_chaos()
var/chaos_levels = CONFIG_GET(keyed_list/chaos_level)
if(config_tag in chaos_levels)
return chaos_levels[config_tag]
else
return chaos
+1
View File
@@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(gangs)
name = "gang war"
config_tag = "gang"
antag_flag = ROLE_GANG
chaos = 9
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 15
+1
View File
@@ -2,6 +2,7 @@
name = "meteor"
config_tag = "meteor"
false_report_weight = 1
chaos = 9
var/meteordelay = 2000
var/nometeors = 0
var/rampupdelta = 5
+1
View File
@@ -11,6 +11,7 @@
required_players = 20
required_enemies = 1
recommended_enemies = 1
chaos = 9
restricted_jobs = list("Cyborg", "AI")
+1
View File
@@ -2,6 +2,7 @@
name = "nuclear emergency"
config_tag = "nuclear"
false_report_weight = 10
chaos = 9
required_players = 28 // 30 players - 3 players to be the nuke ops = 25 players remaining
required_enemies = 2
recommended_enemies = 5
@@ -3,6 +3,7 @@
name = "overthrow"
config_tag = "overthrow"
antag_flag = ROLE_OVERTHROW
chaos = 5
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20 // the core idea is of a swift, bloodless coup, so it shouldn't be as chaotic as revs.
@@ -12,6 +12,7 @@
config_tag = "revolution"
antag_flag = ROLE_REV
false_report_weight = 10
chaos = 8
restricted_jobs = list("AI", "Cyborg")
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_players = 20
@@ -10,6 +10,7 @@
required_enemies = 5
recommended_enemies = 8
reroll_friendly = 0
chaos = 7
traitor_name = "Nanotrasen Internal Affairs Agent"
antag_flag = ROLE_INTERNAL_AFFAIRS
+1
View File
@@ -17,6 +17,7 @@
recommended_enemies = 4
reroll_friendly = 1
enemy_minimum_age = 0
chaos = 2
announce_span = "danger"
announce_text = "There are Syndicate agents on the station!\n\
+1
View File
@@ -12,6 +12,7 @@
recommended_enemies = 1
enemy_minimum_age = 7
round_ends_with_antag_death = 1
chaos = 9
announce_span = "danger"
announce_text = "There is a space wizard attacking the station!\n\
<span class='danger'>Wizard</span>: Accomplish your objectives and cause mayhem on the station.\n\
+10 -5
View File
@@ -137,7 +137,7 @@ Class Procs:
GLOB.machines += src
if(ispath(circuit, /obj/item/circuitboard))
circuit = new circuit
circuit = new circuit(src)
circuit.apply_default_parts(src)
if(!speed_process && init_process)
@@ -361,11 +361,11 @@ Class Procs:
/obj/machinery/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
on_deconstruction()
if(component_parts && component_parts.len)
if(LAZYLEN(component_parts))
spawn_frame(disassembled)
for(var/obj/item/I in component_parts)
I.forceMove(loc)
component_parts.Cut()
LAZYCLEARLIST(component_parts)
qdel(src)
/obj/machinery/proc/spawn_frame(disassembled)
@@ -539,12 +539,17 @@ Class Procs:
/obj/machinery/Exited(atom/movable/AM, atom/newloc)
. = ..()
// if(AM == occupant)
// set_occupant(null)
if (AM == occupant)
SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
occupant = null
if(AM == circuit && circuit.loc != src)
component_parts -= AM //TODO: make the cmp part functions use lazyX
circuit = null
/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8
var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot.
/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8
var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot.
for (var/i in 1 to 32)
. += hex2num(md5[i])
. = . % 9
+13 -1
View File
@@ -8,7 +8,19 @@
max_integrity = 200
var/obj/item/bodypart/storedpart
var/initial_icon_state
var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi', "engineer" = 'icons/mob/augmentation/augments_engineer.dmi', "security" = 'icons/mob/augmentation/augments_security.dmi', "mining" = 'icons/mob/augmentation/augments_mining.dmi')
var/static/list/style_list_icons = list("standard" = 'icons/mob/augmentation/augments.dmi',
"engineer" = 'icons/mob/augmentation/augments_engineer.dmi',
"security" = 'icons/mob/augmentation/augments_security.dmi',
"mining" = 'icons/mob/augmentation/augments_mining.dmi',
"Talon" = 'icons/mob/augmentation/cosmetic_prosthetic/talon.dmi',
"Nanotrasen" = 'icons/mob/augmentation/cosmetic_prosthetic/nanotrasen.dmi',
"Hephaesthus" = 'icons/mob/augmentation/cosmetic_prosthetic/hephaestus.dmi',
"Bishop" = 'icons/mob/augmentation/cosmetic_prosthetic/bishop.dmi',
"Xion" = 'icons/mob/augmentation/cosmetic_prosthetic/xion.dmi',
"Grayson" = 'icons/mob/augmentation/cosmetic_prosthetic/grayson.dmi',
"Cybersolutions" = 'icons/mob/augmentation/cosmetic_prosthetic/cybersolutions.dmi',
"Ward" = 'icons/mob/augmentation/cosmetic_prosthetic/ward.dmi'
)
/obj/machinery/aug_manipulator/examine(mob/user)
. = ..()
+31 -23
View File
@@ -17,20 +17,16 @@
/obj/machinery/computer/Initialize(mapload, obj/item/circuitboard/C)
. = ..()
power_change()
if(!QDELETED(C))
qdel(circuit)
circuit = C
C.moveToNullspace()
/obj/machinery/computer/Destroy()
QDEL_NULL(circuit)
return ..()
. = ..()
/obj/machinery/computer/process()
if(stat & (NOPOWER|BROKEN))
return 0
return 1
return FALSE
return TRUE
/obj/machinery/computer/ratvar_act()
if(!clockwork)
@@ -87,25 +83,32 @@
switch(damage_type)
if(BRUTE)
if(stat & BROKEN)
playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
playsound(src.loc, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE)
else
playsound(src.loc, 'sound/effects/glasshit.ogg', 75, 1)
playsound(src.loc, 'sound/effects/glasshit.ogg', 75, TRUE)
if(BURN)
playsound(src.loc, 'sound/items/welder.ogg', 100, 1)
playsound(src.loc, 'sound/items/welder.ogg', 100, TRUE)
/obj/machinery/computer/obj_break(damage_flag)
if(circuit && !(flags_1 & NODECONSTRUCT_1)) //no circuit, no breaking
if(!(stat & BROKEN))
playsound(loc, 'sound/effects/glassbr3.ogg', 100, 1)
stat |= BROKEN
update_icon()
set_light(0)
if(!circuit) //no circuit, no breaking
return
. = ..()
if(. && !(stat & BROKEN))
stat |= BROKEN
playsound(loc, 'sound/effects/glassbr3.ogg', 100, TRUE)
set_light(0)
update_icon()
/obj/machinery/computer/emp_act(severity)
. = ..()
if (!(. & EMP_PROTECT_SELF))
if(prob(severity/1.8))
obj_break("energy")
switch(severity)
if(1)
if(prob(50))
obj_break("energy")
if(2)
if(prob(10))
obj_break("energy")
/obj/machinery/computer/deconstruct(disassembled = TRUE, mob/user)
on_deconstruction()
@@ -114,12 +117,14 @@
var/obj/structure/frame/computer/A = new /obj/structure/frame/computer(src.loc)
A.setDir(dir)
A.circuit = circuit
A.setAnchored(TRUE)
// Circuit removal code is handled in /obj/machinery/Exited()
circuit.forceMove(A)
A.set_anchored(TRUE)
if(stat & BROKEN)
if(user)
to_chat(user, "<span class='notice'>The broken glass falls out.</span>")
else
playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1)
playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, TRUE)
new /obj/item/shard(drop_location())
new /obj/item/shard(drop_location())
A.state = 3
@@ -129,8 +134,11 @@
to_chat(user, "<span class='notice'>You disconnect the monitor.</span>")
A.state = 4
A.icon_state = "4"
circuit = null
for(var/obj/C in src)
C.forceMove(loc)
qdel(src)
/obj/machinery/computer/AltClick(mob/user)
. = ..()
if(!user.canUseTopic(src, !issilicon(user)) || !(stat & (NOPOWER|BROKEN)))
return
@@ -243,13 +243,14 @@
// Check if the user can use it.
/obj/machinery/telecomms/proc/canInteract(mob/user)
var/get = user.get_active_held_item()
var/obj/item/I = get
if(I.tool_behaviour == TOOL_MULTITOOL)
return TRUE
var/obj/item/I = user.get_active_held_item()
if(!issilicon(user) && I)
if(I.tool_behaviour == TOOL_MULTITOOL)
return TRUE
if(hasSiliconAccessInArea(user))
return TRUE
return FALSE
// Check if the user is nearby and has a multitool.
/obj/machinery/telecomms/proc/canAccess(mob/user)
if((canInteract(user) && in_range(user, src)) || hasSiliconAccessInArea(user))
@@ -262,14 +263,13 @@
return null
var/obj/item/P = user.get_active_held_item()
// Is the ref not a null? and is it the actual type?
if(P.tool_behaviour == TOOL_MULTITOOL)
return P
else if(isAI(user))
if(isAI(user))
var/mob/living/silicon/ai/U = user
P = U.aiMulti
else if(iscyborg(user) && in_range(user, src))
var/get = user.get_active_held_item()
var/obj/item/I = get
if(I.tool_behaviour == TOOL_MULTITOOL)
I = user.get_active_held_item()
else if(iscyborg(user) && !in_range(user, src))
return null
if(!P)
return null
else if(P.tool_behaviour == TOOL_MULTITOOL)
return P
return P
+1 -1
View File
@@ -65,7 +65,7 @@
/obj/machinery/mecha_part_fabricator/Initialize(mapload)
stored_research = new
rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init)
rmat = AddComponent(/datum/component/remote_materials, "mechfab", mapload && link_on_init, _after_insert=CALLBACK(src, .proc/AfterMaterialInsert))
RefreshParts() //Recalculating local material sizes if the fab isn't linked
return ..()
+2 -3
View File
@@ -132,7 +132,6 @@
events = new
icon_state += "-open"
add_radio()
add_cabin()
spark_system.set_up(2, 0, src)
spark_system.attach(src)
smoke_system.set_up(3, src)
@@ -153,6 +152,7 @@
/obj/mecha/LateInitialize()
. = ..()
add_airtank()
add_cabin()
/obj/mecha/get_cell()
return cell
@@ -255,9 +255,8 @@
cell = new /obj/item/stock_parts/cell/high/plus(src)
/obj/mecha/proc/add_cabin()
cabin_air = new
cabin_air = new(200)
cabin_air.set_temperature(T20C)
cabin_air.set_volume(200)
cabin_air.set_moles(/datum/gas/oxygen,O2STANDARD*cabin_air.return_volume()/(R_IDEAL_GAS_EQUATION*cabin_air.return_temperature()))
cabin_air.set_moles(/datum/gas/nitrogen,N2STANDARD*cabin_air.return_volume()/(R_IDEAL_GAS_EQUATION*cabin_air.return_temperature()))
return cabin_air
@@ -243,6 +243,17 @@
icon_state = "shieldsparkles"
duration = 5
/obj/effect/temp_visual/voidpush
name = "eldritch energy"
icon_state = "emark4"
duration = 5
/obj/effect/temp_visual/voidswap
name = "altered space"
icon = 'icons/mob/mob.dmi'
icon_state = "voidalter"
duration = 5
/obj/effect/temp_visual/telekinesis
name = "telekinetic force"
icon_state = "empdisable"
+3
View File
@@ -16,6 +16,9 @@
if(isopenturf(target))
deploy_bodybag(user, target)
/obj/item/bodybag/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool)
return (user in src)
/obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location)
var/obj/structure/closet/body_bag/R = new unfoldedbag_path(location)
R.open(user)
+4 -4
View File
@@ -721,24 +721,24 @@
/obj/item/disk/medical/defib_heal
name = "Defibrillator Healing Disk"
desc = "An upgrade which increases the healing power of the defibrillator"
desc = "An upgrade which increases the healing power of the defibrillator."
icon_state = "heal_disk"
custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 18000, /datum/material/gold = 6000, /datum/material/silver = 6000)
/obj/item/disk/medical/defib_shock
name = "Defibrillator Anti-Shock Disk"
desc = "A safety upgrade that guarantees only the patient will get shocked"
desc = "A safety upgrade that guarantees only the patient will get shocked."
icon_state = "zap_disk"
custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 18000, /datum/material/gold = 6000, /datum/material/silver = 6000)
/obj/item/disk/medical/defib_decay
name = "Defibrillator Body-Decay Extender Disk"
desc = "An upgrade allowing the defibrillator to work on more decayed bodies"
desc = "An upgrade allowing the defibrillator to work on bodies that have decayed further."
icon_state = "body_disk"
custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 18000, /datum/material/gold = 16000, /datum/material/silver = 6000, /datum/material/titanium = 2000)
/obj/item/disk/medical/defib_speed
name = "Defibrillator Fast Charge Disk"
desc = "An upgrade to the defibrillator capacitors, which let it charge faster"
desc = "An upgrade to the defibrillator capacitors, which lets it charge faster."
icon_state = "fast_disk"
custom_materials = list(/datum/material/iron=16000, /datum/material/glass = 8000, /datum/material/gold = 26000, /datum/material/silver = 26000)
+1 -2
View File
@@ -3,9 +3,8 @@
/obj/item/melee/proc/check_martial_counter(mob/living/carbon/human/target, mob/living/carbon/human/user)
if(target.check_martial_melee_block())
target.visible_message("<span class='danger'>[target.name] blocks [src] and twists [user]'s arm behind [user.p_their()] back!</span>",
target.visible_message("<span class='danger'>[target.name] blocks your attack!</span>",
"<span class='userdanger'>You block the attack!</span>")
user.Stun(40)
return TRUE
/obj/item/melee/chainofcommand
+11
View File
@@ -332,6 +332,7 @@
to_chat(occupant, "You pop out of the [src], slightly dazed!")
occupant.Stun(5 SECONDS)
/obj/item/pet_carrier/bluespace/return_air()
if(!occupant_gas_supply)
occupant_gas_supply = new
@@ -353,4 +354,14 @@
if(..())
name = "[initial(name)] ([target])"
/obj/item/pet_carrier/bluespace/single_use
desc = "A jar, that seems to be bigger on the inside, somehow allowing lifeforms to fit through its narrow entrance. This one looks exceptionally fragile."
/obj/item/pet_carrier/bluespace/single_use/remove_occupant(mob/living/occupant)
. = ..()
if(!QDELETED(src))
playsound(src, "shatter", 70, 1)
qdel(src)
#undef pet_carrier_full
+1 -1
View File
@@ -469,7 +469,7 @@
//hijacking the minature first aids for hypospray boxes. <3
/obj/item/storage/hypospraykit
name = "hypospray kit"
desc = "It's a kit containing a hypospray and specific treatment chemical-filled vials."
desc = "It's a kit designed for containing a hypospray and specific treatment chemical-filled vials."
icon_state = "firstaid-mini"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
@@ -58,7 +58,6 @@
/obj/item/tank/internals/air
name = "air tank"
desc = "Mixed anyone?"
icon_state = "air"
item_state = "air"
force = 10
dog_fashion = /datum/dog_fashion/back
+2 -2
View File
@@ -80,9 +80,9 @@
SStgui.close_uis(src)
. = ..()
/// @depricated DO NOT USE
/obj/proc/setAnchored(anchorvalue)
SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue)
anchored = anchorvalue
set_anchored(anchorvalue)
/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE)
. = ..()
+108 -58
View File
@@ -16,12 +16,12 @@
//Adding canvases
/obj/structure/easel/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/canvas))
var/obj/item/canvas/C = I
user.dropItemToGround(C)
painting = C
C.forceMove(get_turf(src))
C.layer = layer+0.1
user.visible_message("<span class='notice'>[user] puts \the [C] on \the [src].</span>","<span class='notice'>You place \the [C] on \the [src].</span>")
var/obj/item/canvas/canvas = I
user.dropItemToGround(canvas)
painting = canvas
canvas.forceMove(get_turf(src))
canvas.layer = layer+0.1
user.visible_message("<span class='notice'>[user] puts \the [canvas] on \the [src].</span>","<span class='notice'>You place \the [canvas] on \the [src].</span>")
else
return ..()
@@ -40,13 +40,14 @@
desc = "Draw out your soul on this canvas!"
icon = 'icons/obj/artstuff.dmi'
icon_state = "11x11"
// flags_1 = UNPAINTABLE_1
resistance_flags = FLAMMABLE
var/width = 11
var/height = 11
var/list/grid
var/canvas_color = "#ffffff" //empty canvas color
var/used = FALSE
var/painting_name //Painting name, this is set after framing.
var/painting_name = "Untitled Artwork" //Painting name, this is set after framing.
var/finalized = FALSE //Blocks edits
var/author_ckey
var/icon_generated = FALSE
@@ -132,17 +133,19 @@
/obj/item/canvas/update_overlays()
. = ..()
if(!icon_generated)
if(used)
var/mutable_appearance/detail = mutable_appearance(icon,"[icon_state]wip")
detail.pixel_x = 1
detail.pixel_y = 1
. += detail
else
if(icon_generated)
var/mutable_appearance/detail = mutable_appearance(generated_icon)
detail.pixel_x = 1
detail.pixel_y = 1
. += detail
return
if(!used)
return
var/mutable_appearance/detail = mutable_appearance(icon, "[icon_state]wip")
detail.pixel_x = 1
detail.pixel_y = 1
. += detail
/obj/item/canvas/proc/generate_proper_overlay()
if(icon_generated)
@@ -167,8 +170,8 @@
if(!I)
return
if(istype(I, /obj/item/toy/crayon))
var/obj/item/toy/crayon/C = I
return C.paint_color
var/obj/item/toy/crayon/crayon = I
return crayon.paint_color
else if(istype(I, /obj/item/pen))
var/obj/item/pen/P = I
switch(P.colour)
@@ -184,7 +187,7 @@
/obj/item/canvas/proc/try_rename(mob/user)
var/new_name = stripped_input(user,"What do you want to name the painting?")
if(!painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
if(new_name != painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
painting_name = new_name
SStgui.update_uis(src)
@@ -215,12 +218,23 @@
framed_offset_x = 5
framed_offset_y = 6
/obj/item/canvas/twentyfour_twentyfour
name = "ai universal standard canvas"
desc = "Besides being very large, the AI can accept these as a display from their internal database after you've hung it up."
icon_state = "24x24"
width = 24
height = 24
pixel_x = 2
pixel_y = 1
framed_offset_x = 4
framed_offset_y = 5
/obj/item/wallframe/painting
name = "painting frame"
desc = "The perfect showcase for your favorite deathtrap memories."
icon = 'icons/obj/decals.dmi'
custom_materials = null
flags_1 = 0
custom_materials = list(/datum/material/wood = 2000)
flags_1 = NONE
icon_state = "frame-empty"
result_path = /obj/structure/sign/painting
@@ -229,8 +243,13 @@
desc = "Art or \"Art\"? You decide."
icon = 'icons/obj/decals.dmi'
icon_state = "frame-empty"
// base_icon_state = "frame"
custom_materials = list(/datum/material/wood = 2000)
buildable_sign = FALSE
var/obj/item/canvas/C
///Canvas we're currently displaying.
var/obj/item/canvas/current_canvas
///Description set when canvas is added.
var/desc_with_canvas
var/persistence_id
/obj/structure/sign/painting/Initialize(mapload, dir, building)
@@ -242,64 +261,78 @@
if(building)
pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30)
pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0
desc = current_canvas ? desc_with_canvas : initial(desc)
/obj/structure/sign/painting/Destroy()
. = ..()
SSpersistence.painting_frames -= src
/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params)
if(!C && istype(I, /obj/item/canvas))
if(!current_canvas && istype(I, /obj/item/canvas))
frame_canvas(user,I)
else if(C && !C.painting_name && istype(I,/obj/item/pen))
else if(current_canvas && current_canvas.painting_name == initial(current_canvas.painting_name) && istype(I,/obj/item/pen))
try_rename(user)
else
return ..()
/obj/structure/sign/painting/examine(mob/user)
. = ..()
if(C)
C.ui_interact(user)
if(persistence_id)
. += "<span class='notice'>Any painting placed here will be archived at the end of the shift.</span>"
if(current_canvas)
current_canvas.ui_interact(user)
. += "<span class='notice'>Use wirecutters to remove the painting.</span>"
/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I)
. = ..()
if(C)
C.forceMove(drop_location())
C = null
if(current_canvas)
current_canvas.forceMove(drop_location())
current_canvas = null
to_chat(user, "<span class='notice'>You remove the painting from the frame.</span>")
update_icon()
return TRUE
/obj/structure/sign/painting/proc/frame_canvas(mob/user,obj/item/canvas/new_canvas)
if(user.transferItemToLoc(new_canvas,src))
C = new_canvas
if(!C.finalized)
C.finalize(user)
to_chat(user,"<span class='notice'>You frame [C].</span>")
current_canvas = new_canvas
if(!current_canvas.finalized)
current_canvas.finalize(user)
to_chat(user,"<span class='notice'>You frame [current_canvas].</span>")
update_icon()
/obj/structure/sign/painting/proc/try_rename(mob/user)
if(!C.painting_name)
C.try_rename(user)
if(current_canvas.painting_name == initial(current_canvas.painting_name))
current_canvas.try_rename(user)
// /obj/structure/sign/painting/update_name(updates)
// name = current_canvas ? "painting - [current_canvas.painting_name]" : initial(name)
// return ..()
// /obj/structure/sign/painting/update_desc(updates)
// desc = current_canvas ? desc_with_canvas : initial(desc)
// return ..()
/obj/structure/sign/painting/update_icon_state()
. = ..()
if(C && C.generated_icon)
icon_state = null
else
// icon_state = "[base_icon_state]-[current_canvas?.generated_icon ? "overlay" : "empty"]"
if(current_canvas?.generated_icon)
icon_state = "frame-empty"
else
icon_state = null // or "frame-empty"
return ..()
/obj/structure/sign/painting/update_overlays()
. = ..()
if(C && C.generated_icon)
var/mutable_appearance/MA = mutable_appearance(C.generated_icon)
MA.pixel_x = C.framed_offset_x
MA.pixel_y = C.framed_offset_y
. += MA
var/mutable_appearance/frame = mutable_appearance(C.icon,"[C.icon_state]frame")
frame.pixel_x = C.framed_offset_x - 1
frame.pixel_y = C.framed_offset_y - 1
. += frame
if(!current_canvas?.generated_icon)
return
var/mutable_appearance/MA = mutable_appearance(current_canvas.generated_icon)
MA.pixel_x = current_canvas.framed_offset_x
MA.pixel_y = current_canvas.framed_offset_y
. += MA
var/mutable_appearance/frame = mutable_appearance(current_canvas.icon,"[current_canvas.icon_state]frame")
frame.pixel_x = current_canvas.framed_offset_x - 1
frame.pixel_y = current_canvas.framed_offset_y - 1
. += frame
/obj/structure/sign/painting/proc/load_persistent()
if(!persistence_id)
@@ -310,6 +343,10 @@
var/title = chosen["title"]
var/author = chosen["ckey"]
var/png = "data/paintings/[persistence_id]/[chosen["md5"]].png"
if(!title)
title = "Untitled Artwork" //Should prevent NULL named art from loading as NULL, if you're still getting the admin log chances are persistence is broken
if(!title)
message_admins("<span class='notice'>Painting with NO TITLE loaded on a [persistence_id] frame in [get_area(src)]. Please delete it, it is saved in the database with no name and will create bad assets.</span>")
if(!fexists(png))
stack_trace("Persistent painting [chosen["md5"]].png was not found in [persistence_id] directory.")
return
@@ -328,17 +365,20 @@
new_canvas.finalized = TRUE
new_canvas.painting_name = title
new_canvas.author_ckey = author
C = new_canvas
new_canvas.name = "painting - [title]"
current_canvas = new_canvas
update_icon()
/obj/structure/sign/painting/proc/save_persistent()
if(!persistence_id || !C)
if(!persistence_id || !current_canvas)
return
if(sanitize_filename(persistence_id) != persistence_id)
stack_trace("Invalid persistence_id - [persistence_id]")
return
var/data = C.get_data_string()
var/md5 = md5(data)
if(!current_canvas.painting_name)
current_canvas.painting_name = "Untitled Artwork"
var/data = current_canvas.get_data_string()
var/md5 = md5(lowertext(data))
var/list/current = SSpersistence.paintings[persistence_id]
if(!current)
current = list()
@@ -347,10 +387,10 @@
return
var/png_directory = "data/paintings/[persistence_id]/"
var/png_path = png_directory + "[md5].png"
var/result = rustg_dmi_create_png(png_path,"[C.width]","[C.height]",data)
var/result = rustg_dmi_create_png(png_path,"[current_canvas.width]","[current_canvas.height]",data)
if(result)
CRASH("Error saving persistent painting: [result]")
current += list(list("title" = C.painting_name , "md5" = md5, "ckey" = C.author_ckey))
current += list(list("title" = current_canvas.painting_name , "md5" = md5, "ckey" = current_canvas.author_ckey))
SSpersistence.paintings[persistence_id] = current
/obj/item/canvas/proc/fill_grid_from_icon(icon/I)
@@ -361,12 +401,21 @@
//Presets for art gallery mapping, for paintings to be shared across stations
/obj/structure/sign/painting/library
name = "\improper Public Painting Exhibit mounting"
desc = "For art pieces hung by the public."
desc_with_canvas = "A piece of art (or \"art\"). Anyone could've hung it."
persistence_id = "library"
/obj/structure/sign/painting/library_secure
name = "\improper Curated Painting Exhibit mounting"
desc = "For masterpieces hand-picked by the curator."
desc_with_canvas = "A masterpiece hand-picked by the curator, supposedly."
persistence_id = "library_secure"
/obj/structure/sign/painting/library_private // keep your smut away from prying eyes, or non-librarians at least
name = "\improper Private Painting Exhibit mounting"
desc = "For art pieces deemed too subversive or too illegal to be shared outside of curators."
desc_with_canvas = "A painting hung away from lesser minds."
persistence_id = "library_private"
/obj/structure/sign/painting/vv_get_dropdown()
@@ -379,11 +428,11 @@
if(!check_rights(NONE))
return
var/mob/user = usr
if(!persistence_id || !C)
if(!persistence_id || !current_canvas)
to_chat(user,"<span class='warning'>This is not a persistent painting.</span>")
return
var/md5 = md5(C.get_data_string())
var/author = C.author_ckey
var/md5 = md5(lowertext(current_canvas.get_data_string()))
var/author = current_canvas.author_ckey
var/list/current = SSpersistence.paintings[persistence_id]
if(current)
for(var/list/entry in current)
@@ -392,7 +441,8 @@
var/png = "data/paintings/[persistence_id]/[md5].png"
fdel(png)
for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames)
if(P.C && md5(P.C.get_data_string()) == md5)
QDEL_NULL(P.C)
if(P.current_canvas && md5(P.current_canvas.get_data_string()) == md5)
QDEL_NULL(P.current_canvas)
P.update_icon()
log_admin("[key_name(user)] has deleted a persistent painting made by [author].")
message_admins("<span class='notice'>[key_name_admin(user)] has deleted persistent painting made by [author].</span>")
@@ -626,4 +626,4 @@
T1.visible_message("<span class='warning'>[user] dives into [src]!</span>")
/obj/structure/closet/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool)
return ..() && opened
return (user in src)