diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 97d4d34fc66..c955baea66f 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -46,6 +46,10 @@ #define CLIENT_FROM_VAR(I) (ismob(I) ? I:client : (isclient(I) ? I : null)) + +//Persistence +#define AREA_FLAG_IS_NOT_PERSISTENT 8 // SSpersistence will not track values from this area. + // Shuttles. // These define the time taken for the shuttle to get to the space station, and the time before it leaves again. @@ -351,3 +355,28 @@ var/global/list/##LIST_NAME = list();\ #define JOB_SILICON 0x6 // 2|4, probably don't set jobs to this, but good for checking #define DEFAULT_OVERMAP_RANGE 0 // Makes general computers and devices be able to connect to other overmap z-levels on the same tile. + + +//Various stuff used in Persistence + +#define send_output(target, msg, control) target << output(msg, control) + +#define send_link(target, url) target << link(url) + +#define SPAN_NOTICE(X) "[X]" + +#define SPAN_WARNING(X) "[X]" + +#define SPAN_DANGER(X) "[X]" + +#define SPAN_OCCULT(X) "[X]" + +#define FONT_SMALL(X) "[X]" + +#define FONT_NORMAL(X) "[X]" + +#define FONT_LARGE(X) "[X]" + +#define FONT_HUGE(X) "[X]" + +#define FONT_GIANT(X) "[X]" \ No newline at end of file diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 94228f34399..79b82bfe224 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -77,6 +77,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_AI -22 #define INIT_ORDER_AI_FAST -23 #define INIT_ORDER_GAME_MASTER -24 +#define INIT_ORDER_PERSISTENCE -25 #define INIT_ORDER_TICKER -50 #define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. diff --git a/code/_helpers/global_lists_vr.dm b/code/_helpers/global_lists_vr.dm index 498f497a7c8..b2dfae05444 100644 --- a/code/_helpers/global_lists_vr.dm +++ b/code/_helpers/global_lists_vr.dm @@ -154,7 +154,7 @@ var/global/list/edible_trash = list(/obj/item/broken_device, /obj/item/weapon/bone, /obj/item/weapon/broken_bottle, /obj/item/weapon/card/emag_broken, - /obj/item/weapon/cigbutt, + /obj/item/trash/cigbutt, /obj/item/weapon/circuitboard/broken, /obj/item/weapon/clipboard, /obj/item/weapon/corncob, diff --git a/code/_macros.dm b/code/_macros.dm index 5b7fb379d30..4796f93b472 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -37,4 +37,4 @@ #define random_id(key,min_id,max_id) uniqueness_repository.Generate(/datum/uniqueness_generator/id_random, key, min_id, max_id) -#define ARGS_DEBUG log_debug("[__FILE__] - [__LINE__]") ; for(var/arg in args) { log_debug("\t[log_info_line(arg)]") } +#define ARGS_DEBUG log_debug("[__FILE__] - [__LINE__]") ; for(var/arg in args) { log_debug("\t[log_info_line(arg)]") } \ No newline at end of file diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 3a649b4ad49..ed77065e19f 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -236,6 +236,8 @@ var/list/gamemode_cache = list() var/static/dooc_allowed = 1 var/static/dsay_allowed = 1 + var/persistence_enabled = 1 + var/allow_byond_links = 0 var/allow_discord_links = 0 var/allow_url_links = 0 // honestly if I were you i'd leave this one off, only use in dire situations @@ -434,15 +436,15 @@ var/list/gamemode_cache = list() if ("allow_admin_spawning") config.allow_admin_spawning = 1 - + if ("allow_byond_links") allow_byond_links = 1 if ("allow_discord_links") - allow_discord_links = 1 + allow_discord_links = 1 if ("allow_url_links") - allow_url_links = 1 + allow_url_links = 1 if ("no_dead_vote") config.vote_no_dead = 1 @@ -577,6 +579,9 @@ var/list/gamemode_cache = list() if("protect_roles_from_antagonist") config.protect_roles_from_antagonist = 1 + if ("persistence_enabled") + config.persistence_enabled = 1 + if ("probability") var/prob_pos = findtext(value, " ") var/prob_name = null diff --git a/code/controllers/subsystems/persistence.dm b/code/controllers/subsystems/persistence.dm new file mode 100644 index 00000000000..49f3d601fee --- /dev/null +++ b/code/controllers/subsystems/persistence.dm @@ -0,0 +1,59 @@ +SUBSYSTEM_DEF(persistence) + name = "Persistence" + init_order = INIT_ORDER_PERSISTENCE + flags = SS_NO_FIRE + var/list/tracking_values = list() + var/list/persistence_datums = list() + +/datum/controller/subsystem/persistence/Initialize() + . = ..() + for(var/thing in subtypesof(/datum/persistent)) + var/datum/persistent/P = new thing + persistence_datums[thing] = P + P.Initialize() + +/datum/controller/subsystem/persistence/Shutdown() + for(var/thing in persistence_datums) + var/datum/persistent/P = persistence_datums[thing] + P.Shutdown() + +/datum/controller/subsystem/persistence/proc/track_value(var/atom/value, var/track_type) + + if(config.persistence_enabled == 0) //if the config is not set to persistent nothing will save or load. + return + + var/turf/T = get_turf(value) + if(!T) + return + + var/area/A = get_area(T) + if(!A || (A.flags & AREA_FLAG_IS_NOT_PERSISTENT)) + return + +// if((!T.z in GLOB.using_map.station_levels) || !initialized) + if(!T.z in using_map.station_levels) + return + + if(!tracking_values[track_type]) + tracking_values[track_type] = list() + tracking_values[track_type] += value + +/datum/controller/subsystem/persistence/proc/forget_value(var/atom/value, var/track_type) + if(tracking_values[track_type]) + tracking_values[track_type] -= value + + +/datum/controller/subsystem/persistence/proc/show_info(var/mob/user) + if(!user.client.holder) + return + + var/list/dat = list("") + var/can_modify = check_rights(R_ADMIN, 0, user) + for(var/thing in persistence_datums) + var/datum/persistent/P = persistence_datums[thing] + if(P.has_admin_data) + dat += P.GetAdminSummary(user, can_modify) + dat += "
" + var/datum/browser/popup = new(user, "admin_persistence", "Persistence Data") + popup.set_content(jointext(dat, null)) + popup.open() \ No newline at end of file diff --git a/code/datums/supplypacks/supply.dm b/code/datums/supplypacks/supply.dm index 0863ec8cbb7..5c35e2ae6fd 100644 --- a/code/datums/supplypacks/supply.dm +++ b/code/datums/supplypacks/supply.dm @@ -85,6 +85,12 @@ containertype = /obj/structure/closet/crate/ummarcar containername = "Office supplies crate" +/datum/supply_pack/supply/sticky_notes + name = "Stationery - sticky notes (50)" + contains = list(/obj/item/sticky_pad/random) + cost = 10 + containername = "\improper Sticky notes crate" + /datum/supply_pack/supply/spare_pda name = "Spare PDAs" cost = 10 diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index d9c1ec53ea7..4d4533b4b31 100755 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -31,6 +31,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station power_environ = 0 base_turf = /turf/space ambience = AMBIENCE_SPACE + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/space/atmosalert() return @@ -68,7 +69,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/shuttle requires_power = 0 - flags = RAD_SHIELDED + flags = RAD_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT sound_env = SMALL_ENCLOSED base_turf = /turf/space forbid_events = TRUE @@ -201,6 +202,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Alien base" icon_state = "yellow" requires_power = 0 + flags = AREA_FLAG_IS_NOT_PERSISTENT // CENTCOM @@ -209,6 +211,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station icon_state = "centcom" requires_power = 0 dynamic_lighting = 0 + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/centcom/control name = "\improper CentCom Control" @@ -286,6 +289,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station requires_power = 0 dynamic_lighting = 0 ambience = AMBIENCE_HIGHSEC + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/syndicate_mothership/control name = "\improper Mercenary Control Room" @@ -302,6 +306,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station icon_state = "asteroid" requires_power = 0 sound_env = ASTEROID + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/asteroid/cave // -- TLE name = "\improper Moon - Underground" @@ -325,6 +330,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station requires_power = 0 dynamic_lighting = 0 sound_env = ARENA + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/tdome/tdome1 name = "\improper Thunderdome (Team 1)" @@ -352,6 +358,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station flags = RAD_SHIELDED base_turf = /turf/space ambience = AMBIENCE_HIGHSEC + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/syndicate_station/start name = "\improper Mercenary Forward Operating Base" @@ -407,6 +414,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station requires_power = 0 dynamic_lighting = 0 ambience = AMBIENCE_OTHERWORLDLY + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/skipjack_station name = "\improper Skipjack" @@ -414,6 +422,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station requires_power = 0 base_turf = /turf/space ambience = AMBIENCE_HIGHSEC + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/skipjack_station/start name = "\improper Skipjack" @@ -448,6 +457,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Prison Station" icon_state = "brig" ambience = AMBIENCE_HIGHSEC + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/prison/arrival_airlock name = "\improper Prison Station Airlock" @@ -633,6 +643,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/maintenance/disposal name = "Waste Disposal" icon_state = "disposal" + flags = AREA_FLAG_IS_NOT_PERSISTENT //If trash items got this far, they can be safely deleted. /area/maintenance/engineering name = "Engineering Maintenance" @@ -946,10 +957,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/crew_quarters/heads/hop name = "\improper Command - HoP's Office" icon_state = "head_quarters" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/crew_quarters/heads/hor name = "\improper Research - RD's Office" icon_state = "head_quarters" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/crew_quarters/heads/chief name = "\improper Engineering - CE's Office" @@ -962,6 +975,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/crew_quarters/heads/cmo name = "\improper Medbay - CMO's Office" icon_state = "head_quarters" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/crew_quarters/courtroom name = "\improper Courtroom" @@ -1288,6 +1302,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station dynamic_lighting = 0 sound_env = LARGE_ENCLOSED forbid_events = TRUE + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/holodeck/alphadeck name = "\improper Holodeck Alpha" @@ -1648,10 +1663,12 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/medical/surgery name = "\improper Operating Theatre 1" icon_state = "surgery" + flags = AREA_FLAG_IS_NOT_PERSISTENT //This WOULD become a filth pit /area/medical/surgery2 name = "\improper Operating Theatre 2" icon_state = "surgery" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/medical/surgeryobs name = "\improper Operation Observation Room" @@ -1688,6 +1705,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/medical/sleeper name = "\improper Emergency Treatment Centre" icon_state = "exam_room" + flags = AREA_FLAG_IS_NOT_PERSISTENT //Trust me. /area/medical/first_aid_station_starboard name = "\improper Starboard First-Aid Station" @@ -1902,6 +1920,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/quartermaster/delivery name = "\improper Cargo - Delivery Office" icon_state = "quart" + flags = AREA_FLAG_IS_NOT_PERSISTENT //So trash doesn't pile up too hard. /area/quartermaster/miningdock name = "\improper Cargo Mining Dock" @@ -1941,6 +1960,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/rnd/rdoffice name = "\improper Research Director's Office" icon_state = "head_quarters" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/rnd/supermatter name = "\improper Supermatter Lab" @@ -2063,6 +2083,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Derelict Station" icon_state = "storage" ambience = AMBIENCE_RUINS + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/derelict/hallway/primary name = "\improper Derelict Primary Hallway" @@ -2163,6 +2184,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/constructionsite name = "\improper Construction Site" icon_state = "storage" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/constructionsite/storage name = "\improper Construction Site Storage Area" @@ -2348,6 +2370,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station /area/wreck ambience = AMBIENCE_RUINS + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/wreck/ai name = "\improper AI Chamber" @@ -2424,6 +2447,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Strange Location" icon_state = "away" ambience = AMBIENCE_FOREBODING + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/awaymission/gateway name = "\improper Gateway" diff --git a/code/game/area/asteroid_areas.dm b/code/game/area/asteroid_areas.dm index e5f22a77d76..85f3bffd2fa 100644 --- a/code/game/area/asteroid_areas.dm +++ b/code/game/area/asteroid_areas.dm @@ -3,6 +3,7 @@ /area/mine icon_state = "mining" sound_env = ASTEROID + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/mine/explored name = "Mine" @@ -27,18 +28,22 @@ /area/outpost/mining_north name = "North Mining Outpost" icon_state = "outpost_mine_north" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/outpost/mining_west name = "West Mining Outpost" icon_state = "outpost_mine_west" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/outpost/abandoned name = "Abandoned Outpost" icon_state = "dark" + flags = AREA_FLAG_IS_NOT_PERSISTENT // Main mining outpost /area/outpost/mining_main icon_state = "outpost_mine_main" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/outpost/mining_main/airlock name = "Mining Outpost Airlock" @@ -90,6 +95,7 @@ // Engineering Outpost /area/outpost/engineering icon_state = "outpost_engine" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/outpost/engineering/hallway name = "Engineering Outpost Hallway" @@ -163,6 +169,7 @@ // Research Outpost /area/outpost/research icon_state = "outpost_research" + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/outpost/research/hallway name = "Research Outpost Hallway" diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index ce0f117e5a3..ee02464c1e7 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -20,6 +20,8 @@ var/global/list/image/splatter_cache=list() var/synthblood = 0 var/list/datum/disease2/disease/virus2 = list() var/amount = 5 + generic_filth = TRUE + persistent = FALSE /obj/effect/decal/cleanable/blood/reveal_blood() if(!fluorescent) diff --git a/code/game/objects/effects/decals/Cleanable/robots.dm b/code/game/objects/effects/decals/Cleanable/robots.dm index 1f49fc9f666..bdc5c9237a6 100644 --- a/code/game/objects/effects/decals/Cleanable/robots.dm +++ b/code/game/objects/effects/decals/Cleanable/robots.dm @@ -5,6 +5,8 @@ icon_state = "gib1" basecolor = SYNTH_BLOOD_COLOUR random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") + generic_filth = FALSE + persistent = FALSE /obj/effect/decal/cleanable/blood/gibs/robot/update_icon() color = "#FFFFFF" @@ -39,6 +41,8 @@ /obj/effect/decal/cleanable/blood/oil basecolor = SYNTH_BLOOD_COLOUR + generic_filth = FALSE + persistent = FALSE /obj/effect/decal/cleanable/blood/oil/dry() return diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index f7965e092d0..179b996df91 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,7 +1,22 @@ /obj/effect/decal/cleanable plane = DIRTY_PLANE + var/persistent = FALSE + var/generic_filth = FALSE + var/age = 0 var/list/random_icon_states = list() +/obj/effect/decal/cleanable/Initialize(var/ml, var/_age) + if(!isnull(_age)) + age = _age + if(random_icon_states && length(src.random_icon_states) > 0) + src.icon_state = pick(src.random_icon_states) + SSpersistence.track_value(src, /datum/persistent/filth) + . = ..() + +/obj/effect/decal/cleanable/Destroy() + SSpersistence.forget_value(src, /datum/persistent/filth) + . = ..() + /obj/effect/decal/cleanable/clean_blood(var/ignore = 0) if(!ignore) qdel(src) diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index d6f42bee8f4..09cee5bb4c2 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -10,7 +10,7 @@ origin_tech = list(TECH_ILLEGAL = 4, TECH_MAGNET = 4) var/can_use = 1 var/obj/effect/dummy/chameleon/active_dummy = null - var/saved_item = /obj/item/weapon/cigbutt + var/saved_item = /obj/item/trash/cigbutt var/saved_icon = 'icons/obj/clothing/masks.dmi' var/saved_icon_state = "cigbutt" var/saved_overlays diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm index 9b4c15605d4..aa4b02b2840 100644 --- a/code/game/objects/items/trash.dm +++ b/code/game/objects/items/trash.dm @@ -7,6 +7,20 @@ w_class = ITEMSIZE_SMALL desc = "This is rubbish." drop_sound = 'sound/items/drop/wrapper.ogg' + var/age = 0 + +/obj/item/trash/New(var/newloc, var/_age) + ..(newloc) + if(!isnull(_age)) + age = _age + +/obj/item/trash/Initialize() + SSpersistence.track_value(src, /datum/persistent/filth/trash) + . = ..() + +/obj/item/trash/Destroy() + SSpersistence.forget_value(src, /datum/persistent/filth/trash) + . = ..() /obj/item/trash/raisins name = "\improper 4no raisins" diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm index 3577ea0a554..1bd2a4eac8d 100644 --- a/code/game/objects/items/weapons/cigs_lighters.dm +++ b/code/game/objects/items/weapons/cigs_lighters.dm @@ -150,7 +150,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM /obj/item/clothing/mask/smokable/examine(mob/user) . = ..() - + if(!is_pipe) var/smoke_percent = round((smoketime / max_smoketime) * 100) switch(smoke_percent) @@ -283,7 +283,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM w_class = ITEMSIZE_TINY slot_flags = SLOT_EARS | SLOT_MASK attack_verb = list("burnt", "singed") - type_butt = /obj/item/weapon/cigbutt + type_butt = /obj/item/trash/cigbutt chem_volume = 15 max_smoketime = 300 smoketime = 300 @@ -341,7 +341,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM name = "premium cigar" desc = "A brown roll of tobacco and... well, you're not quite sure. This thing's huge!" icon_state = "cigar2" - type_butt = /obj/item/weapon/cigbutt/cigarbutt + type_butt = /obj/item/trash/cigbutt/cigarbutt throw_speed = 0.5 item_state = "cigar" max_smoketime = 1500 @@ -369,7 +369,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM chem_volume = 30 nicotine_amt = 10 -/obj/item/weapon/cigbutt +/obj/item/trash/cigbutt name = "cigarette butt" desc = "A manky old cigarette butt." icon = 'icons/obj/clothing/masks.dmi' @@ -379,12 +379,12 @@ CIGARETTE PACKETS ARE IN FANCY.DM slot_flags = SLOT_EARS throwforce = 1 -/obj/item/weapon/cigbutt/Initialize() +/obj/item/trash/cigbutt/Initialize() . = ..() randpixel_xy() transform = turn(transform,rand(0,360)) -/obj/item/weapon/cigbutt/cigarbutt +/obj/item/trash/cigbutt/cigarbutt name = "cigar butt" desc = "A manky old cigar butt." icon_state = "cigarbutt" diff --git a/code/game/objects/items/weapons/material/ashtray.dm b/code/game/objects/items/weapons/material/ashtray.dm index 5cd017f0058..95e558b36aa 100644 --- a/code/game/objects/items/weapons/material/ashtray.dm +++ b/code/game/objects/items/weapons/material/ashtray.dm @@ -46,7 +46,7 @@ var/global/list/ashtray_cache = list() /obj/item/weapon/material/ashtray/attackby(obj/item/weapon/W as obj, mob/user as mob) if (health <= 0) return - if (istype(W,/obj/item/weapon/cigbutt) || istype(W,/obj/item/clothing/mask/smokable/cigarette) || istype(W, /obj/item/weapon/flame/match)) + if (istype(W,/obj/item/trash/cigbutt) || istype(W,/obj/item/clothing/mask/smokable/cigarette) || istype(W, /obj/item/weapon/flame/match)) if (contents.len >= max_butts) to_chat(user, "\The [src] is full.") return diff --git a/code/game/objects/items/weapons/storage/fancy.dm b/code/game/objects/items/weapons/storage/fancy.dm index 926255c6200..f1003c451bf 100644 --- a/code/game/objects/items/weapons/storage/fancy.dm +++ b/code/game/objects/items/weapons/storage/fancy.dm @@ -209,7 +209,7 @@ throwforce = 2 slot_flags = SLOT_BELT storage_slots = 6 - can_hold = list(/obj/item/clothing/mask/smokable/cigarette, /obj/item/weapon/flame/lighter, /obj/item/weapon/cigbutt) + can_hold = list(/obj/item/clothing/mask/smokable/cigarette, /obj/item/weapon/flame/lighter, /obj/item/trash/cigbutt) icon_type = "cigarette" starts_with = list(/obj/item/clothing/mask/smokable/cigarette = 6) var/brand = "\improper Trans-Stellar Duty-free" @@ -316,7 +316,7 @@ throwforce = 2 slot_flags = SLOT_BELT storage_slots = 7 - can_hold = list(/obj/item/clothing/mask/smokable/cigarette/cigar, /obj/item/weapon/cigbutt/cigarbutt) + can_hold = list(/obj/item/clothing/mask/smokable/cigarette/cigar, /obj/item/trash/cigbutt/cigarbutt) icon_type = "cigar" starts_with = list(/obj/item/clothing/mask/smokable/cigarette/cigar = 7) diff --git a/code/game/objects/items/weapons/tape.dm b/code/game/objects/items/weapons/tape.dm index 62153d440b3..90b62ed07b7 100644 --- a/code/game/objects/items/weapons/tape.dm +++ b/code/game/objects/items/weapons/tape.dm @@ -126,7 +126,7 @@ return 1 /obj/item/weapon/tape_roll/proc/stick(var/obj/item/weapon/W, mob/user) - if(!istype(W, /obj/item/weapon/paper)) + if(!istype(W, /obj/item/weapon/paper) || istype(W, /obj/item/weapon/paper/sticky) || !user.unEquip(W)) return user.drop_from_inventory(W) var/obj/item/weapon/ducttape/tape = new(get_turf(src)) diff --git a/code/game/objects/random/_random.dm b/code/game/objects/random/_random.dm index 8b616c83f51..67f82ea1551 100644 --- a/code/game/objects/random/_random.dm +++ b/code/game/objects/random/_random.dm @@ -48,7 +48,6 @@ var/list/random_useful_ if(prob(70)) // Misc. junk if(!random_junk_) random_junk_ = subtypesof(/obj/item/trash) - random_junk_ += typesof(/obj/item/weapon/cigbutt) random_junk_ += /obj/effect/decal/cleanable/spiderling_remains random_junk_ += /obj/effect/decal/remains/mouse random_junk_ += /obj/effect/decal/remains/robot diff --git a/code/game/objects/random/misc.dm b/code/game/objects/random/misc.dm index 944c1145ad3..07a5d46efb3 100644 --- a/code/game/objects/random/misc.dm +++ b/code/game/objects/random/misc.dm @@ -542,8 +542,8 @@ /obj/item/device/assembly/mousetrap/armed, /obj/effect/decal/cleanable/spiderling_remains, /obj/effect/decal/cleanable/ash, - /obj/item/weapon/cigbutt, - /obj/item/weapon/cigbutt/cigarbutt, + /obj/item/trash/cigbutt, + /obj/item/trash/cigbutt/cigarbutt, /obj/effect/decal/remains/mouse) /obj/random/janusmodule diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm deleted file mode 100644 index 629bc1c41e9..00000000000 --- a/code/game/objects/structures/noticeboard.dm +++ /dev/null @@ -1,98 +0,0 @@ -/obj/structure/noticeboard - name = "notice board" - desc = "A board for pinning important notices upon." - icon = 'icons/obj/stationobjs.dmi' - icon_state = "nboard00" - density = 0 - anchored = 1 - var/notices = 0 - -/obj/structure/noticeboard/New(var/loc, var/dir, var/building = 0) - ..() - - if(building) - if(loc) - src.loc = loc - - pixel_x = (dir & 3)? 0 : (dir == 4 ? -32 : 32) - pixel_y = (dir & 3)? (dir ==1 ? -27 : 27) : 0 - update_icon() - return - -/obj/structure/noticeboard/Initialize() - for(var/obj/item/I in loc) - if(notices > 4) break - if(istype(I, /obj/item/weapon/paper)) - I.loc = src - notices++ - icon_state = "nboard0[notices]" - . = ..() - -//attaching papers!! -/obj/structure/noticeboard/attackby(var/obj/item/weapon/O as obj, var/mob/user as mob) - if(istype(O, /obj/item/weapon/paper)) - if(notices < 5) - O.add_fingerprint(user) - add_fingerprint(user) - user.drop_from_inventory(O) - O.loc = src - notices++ - icon_state = "nboard0[notices]" //update sprite - to_chat(user, "You pin the paper to the noticeboard.") - else - to_chat(user, "You reach to pin your paper to the board but hesitate. You are certain your paper will not be seen among the many others already attached.") - if(O.is_wrench()) - to_chat(user, "You start to unwrench the noticeboard.") - playsound(src, O.usesound, 50, 1) - if(do_after(user, 15 * O.toolspeed)) - to_chat(user, "You unwrench the noticeboard.") - new /obj/item/frame/noticeboard( src.loc ) - qdel(src) - return - -/obj/structure/noticeboard/attack_hand(var/mob/user) - user.examinate(src) - -// Since Topic() never seems to interact with usr on more than a superficial -// level, it should be fine to let anyone mess with the board other than ghosts. -/obj/structure/noticeboard/examine(var/mob/user) - . = ..() - if(Adjacent(user)) - var/dat = "Noticeboard
" - for(var/obj/item/weapon/paper/P in src) - dat += "[P.name] Write Remove
" - user << browse("Notices[dat]","window=noticeboard") - onclose(user, "noticeboard") - -/obj/structure/noticeboard/Topic(href, href_list) - ..() - usr.set_machine(src) - if(href_list["remove"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/P = locate(href_list["remove"]) - if(P && P.loc == src) - P.loc = get_turf(src) //dump paper on the floor because you're a clumsy fuck - P.add_fingerprint(usr) - add_fingerprint(usr) - notices-- - icon_state = "nboard0[notices]" - if(href_list["write"]) - if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open - return - var/obj/item/P = locate(href_list["write"]) - if((P && P.loc == src)) //ifthe paper's on the board - var/mob/living/M = usr - if(istype(M)) - var/obj/item/weapon/pen/E = M.get_type_in_hands(/obj/item/weapon/pen) - if(E) - add_fingerprint(M) - P.attackby(E, usr) - else - to_chat(M, "You'll need something to write with!") - if(href_list["read"]) - var/obj/item/weapon/paper/P = locate(href_list["read"]) - if((P && P.loc == src)) - usr << browse("[P.name][P.info]", "window=[P.name]") - onclose(usr, "[P.name]") - return diff --git a/code/game/turfs/flooring/flooring.dm b/code/game/turfs/flooring/flooring.dm index a2f2b62b7f0..7cdc1569c6a 100644 --- a/code/game/turfs/flooring/flooring.dm +++ b/code/game/turfs/flooring/flooring.dm @@ -43,6 +43,7 @@ var/list/flooring_types var/descriptor = "tiles" var/flags var/can_paint + var/can_engrave = FALSE var/list/footstep_sounds = list() // key=species name, value = list of sounds, // For instance, footstep_sounds = list("key" = list(sound.ogg)) var/is_plating = FALSE @@ -320,6 +321,7 @@ var/list/flooring_types flags = TURF_REMOVE_CROWBAR | TURF_CAN_BREAK | TURF_CAN_BURN build_type = /obj/item/stack/tile/floor can_paint = 1 + can_engrave = TRUE footstep_sounds = list("human" = list( 'sound/effects/footstep/floor1.ogg', 'sound/effects/footstep/floor2.ogg', diff --git a/code/game/turfs/initialization/maintenance.dm b/code/game/turfs/initialization/maintenance.dm index 7347ec47db8..9f1ed6dc860 100644 --- a/code/game/turfs/initialization/maintenance.dm +++ b/code/game/turfs/initialization/maintenance.dm @@ -28,7 +28,7 @@ var/global/list/random_junk return /obj/effect/decal/cleanable/generic if(!random_junk) random_junk = subtypesof(/obj/item/trash) - random_junk += typesof(/obj/item/weapon/cigbutt) + random_junk += typesof(/obj/item/trash/cigbutt) random_junk += /obj/effect/decal/cleanable/spiderling_remains random_junk += /obj/effect/decal/remains/mouse random_junk += /obj/effect/decal/remains/robot diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index ff2923005f3..d5b64f4aade 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -73,6 +73,9 @@ /turf/simulated/floor/proc/make_plating(var/place_product, var/defer_icon_update) cut_overlays() + for(var/obj/effect/decal/writing/W in src) + qdel(W) + name = base_name desc = base_desc icon = base_icon @@ -103,6 +106,9 @@ for(var/obj/O in src) O.hide(O.hides_under_flooring() && floored_over) +/turf/simulated/floor/can_engrave() + return (!flooring || flooring.can_engrave) + /turf/simulated/floor/rcd_values(mob/living/user, obj/item/weapon/rcd/the_rcd, passed_mode) switch(passed_mode) if(RCD_FLOORWALL) diff --git a/code/game/turfs/simulated/floor_attackby.dm b/code/game/turfs/simulated/floor_attackby.dm index df13c2162cf..9aa7ecce6de 100644 --- a/code/game/turfs/simulated/floor_attackby.dm +++ b/code/game/turfs/simulated/floor_attackby.dm @@ -1,4 +1,4 @@ -/turf/simulated/floor/attackby(obj/item/C as obj, mob/user as mob) +/turf/simulated/floor/attackby(var/obj/item/C, var/mob/user) if(!C || !user) return 0 @@ -9,6 +9,9 @@ attack_tile(C, L) // Be on help intent if you want to decon something. return + if(!(C.is_screwdriver() && flooring && (flooring.flags & TURF_REMOVE_SCREWDRIVER)) && try_graffiti(user, C)) + return + if(istype(C, /obj/item/stack/tile/roofing)) var/expended_tile = FALSE // To track the case. If a ceiling is built in a multiz zlevel, it also necessarily roofs it against weather var/turf/T = GetAbove(src) diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm index 1ef11c40ded..20267696f3e 100644 --- a/code/game/turfs/simulated/wall_attacks.dm +++ b/code/game/turfs/simulated/wall_attacks.dm @@ -135,9 +135,13 @@ return success_smash(user) return fail_smash(user) -/turf/simulated/wall/attackby(obj/item/weapon/W as obj, mob/user as mob) +/turf/simulated/wall/attackby(var/obj/item/weapon/W, var/mob/user) user.setClickCooldown(user.get_attack_speed(W)) + + if(!construction_stage && try_graffiti(user, W)) + return + if (!user.IsAdvancedToolUser()) to_chat(user, "You don't have the dexterity to do this!") return diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index 1fc49d58a9d..20c54b8fead 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -305,6 +305,9 @@ for(var/obj/machinery/door/airlock/phoron/D in range(3,src)) D.ignite(temperature/4) +/turf/simulated/wall/can_engrave() + return (material && material.hardness >= 10 && material.hardness <= 100) + /turf/simulated/wall/rcd_values(mob/living/user, obj/item/weapon/rcd/the_rcd, passed_mode) if(material.integrity > 1000) // Don't decon things like elevatorium. return FALSE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index e93380d7915..626ee68c71f 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -42,7 +42,7 @@ //Lighting related luminosity = !(dynamic_lighting) has_opaque_atom |= (opacity) - + //Pathfinding related if(movement_cost && pathweight == 1) // This updates pathweight automatically. pathweight = movement_cost @@ -285,6 +285,47 @@ turf/attackby(obj/item/weapon/W as obj, mob/user as mob) /turf/AllowDrop() return TRUE +/turf/proc/can_engrave() + return FALSE + +/turf/proc/try_graffiti(var/mob/vandal, var/obj/item/tool) + + if(!tool.sharp || !can_engrave()) + return FALSE + + if(jobban_isbanned(vandal, "Graffiti")) + to_chat(vandal, SPAN_WARNING("You are banned from leaving persistent information across rounds.")) + return + + var/too_much_graffiti = 0 + for(var/obj/effect/decal/writing/W in src) + too_much_graffiti++ + if(too_much_graffiti >= 5) + to_chat(vandal, "There's too much graffiti here to add more.") + return FALSE + + var/message = sanitize(input("Enter a message to engrave.", "Graffiti") as null|text, trim = TRUE) + if(!message) + return FALSE + + if(!vandal || vandal.incapacitated() || !Adjacent(vandal) || !tool.loc == vandal) + return FALSE + + vandal.visible_message("\The [vandal] begins carving something into \the [src].") + + if(!do_after(vandal, max(20, length(message)), src)) + return FALSE + + vandal.visible_message("\The [vandal] carves some graffiti into \the [src].") + var/obj/effect/decal/writing/graffiti = new(src) + graffiti.message = message + graffiti.author = vandal.ckey + + if(lowertext(message) == "elbereth") + to_chat(vandal, "You feel much safer.") + + return TRUE + // Returns false if stepping into a tile would cause harm (e.g. open space while unable to fly, water tile while a slime, lava, etc). /turf/proc/is_safe_to_enter(mob/living/L) if(LAZYLEN(dangerous_objects)) diff --git a/code/game/world.dm b/code/game/world.dm index 7fefc25cca9..628a12f2e59 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -108,6 +108,7 @@ var/world_topic_spam_protect_time = world.timeofday s["version"] = game_version s["mode"] = master_mode s["respawn"] = config.abandon_allowed + s["persistance"] = config.persistence_enabled s["enter"] = config.enter_allowed s["vote"] = config.allow_vote_mode s["ai"] = config.allow_ai @@ -183,12 +184,12 @@ var/world_topic_spam_protect_time = world.timeofday if(!positions["misc"]) positions["misc"] = list() positions["misc"][name] = rank - + for(var/datum/data/record/t in data_core.hidden_general) var/name = t.fields["name"] var/rank = t.fields["rank"] var/real_rank = make_list_rank(t.fields["real_rank"]) - + var/datum/job/J = SSjob.get_job(real_rank) if(J?.offmap_spawn) if(!positions["off"]) @@ -410,6 +411,7 @@ var/world_topic_spam_protect_time = world.timeofday to_world("Rebooting world immediately due to host request") else Master.Shutdown() //run SS shutdowns + //processScheduler.stop() //VOREStation Removal for(var/client/C in GLOB.clients) if(config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite C << link("byond://[config.server]") @@ -527,6 +529,8 @@ var/world_topic_spam_protect_time = world.timeofday features += config.abandon_allowed ? "respawn" : "no respawn" + features += config.persistence_enabled ? "persistence enabled" : "persistence disabled" + if (config && config.allow_vote_mode) features += "vote" diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index cf6ea072f0a..3ee9ddccb44 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -933,6 +933,20 @@ var/datum/announcement/minor/admin_min_announcer = new world.update_status() feedback_add_details("admin_verb","TR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/datum/admins/proc/togglepersistence() + set category = "Server" + set desc="Whether persistent data will be saved from now on." + set name="Toggle Persistent Data" + config.persistence_enabled = !(config.persistence_enabled) + if(config.persistence_enabled) + to_world("Persistence is now enabled..") + else + to_world("Persistence is no longer enabled.") + message_admins("[key_name_admin(usr)] toggled persistence to [config.persistence_enabled ? "On" : "Off"].", 1) + log_admin("[key_name(usr)] toggled persistence to [config.persistence_enabled ? "On" : "Off"].") + world.update_status() + feedback_add_details("admin_verb","TPD") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + /datum/admins/proc/toggle_aliens() set category = "Server" set desc="Toggle alien mobs" diff --git a/code/modules/admin/admin_verb_lists.dm b/code/modules/admin/admin_verb_lists.dm index 63d02af1f70..b576bec44a9 100644 --- a/code/modules/admin/admin_verb_lists.dm +++ b/code/modules/admin/admin_verb_lists.dm @@ -166,6 +166,7 @@ var/list/admin_verbs_server = list( /datum/admins/proc/restart, /datum/admins/proc/delay, /datum/admins/proc/toggleaban, + /datum/admins/proc/togglepersistence, /client/proc/cmd_mod_say, /client/proc/toggle_log_hrefs, /datum/admins/proc/immreboot, @@ -354,6 +355,7 @@ var/list/admin_verbs_mod = list( /client/proc/allow_character_respawn, // Allows a ghost to respawn , /datum/admins/proc/sendFax, /client/proc/getserverlog, //allows us to fetch server logs (diary) for other days, + /datum/admins/proc/view_persistent_data, /datum/admins/proc/view_txt_log, //shows the server log (diary) for today, /datum/admins/proc/view_atk_log //shows the server combat-log, doesn't do anything presently, ) diff --git a/code/modules/admin/banjob.dm b/code/modules/admin/banjob.dm index 1a3781b0c5f..0bfa4e516a2 100644 --- a/code/modules/admin/banjob.dm +++ b/code/modules/admin/banjob.dm @@ -26,14 +26,18 @@ var/jobban_keylist[0] //to store the keys & ranks if(config.usewhitelist && !check_whitelist(M)) return "Whitelisted Job" - for (var/s in jobban_keylist) - if( findtext(s,"[M.ckey] - [rank]") == 1 ) - var/startpos = findtext(s, "## ")+3 - if(startpos && startpos You Cannot issue temporary job-bans!") + to_chat(usr, " You cannot issue temporary job-bans!") return if(config.ban_legacy_system) to_chat(usr, "Your server is using the legacy banning system, which does not support temporary job bans. Consider upgrading. Aborting ban.") diff --git a/code/modules/materials/material_recipes.dm b/code/modules/materials/material_recipes.dm index fbb842a422d..a5079be7204 100644 --- a/code/modules/materials/material_recipes.dm +++ b/code/modules/materials/material_recipes.dm @@ -159,6 +159,7 @@ recipes += new/datum/stack_recipe("coilgun stock", /obj/item/weapon/coilgun_assembly, 5, pass_stack_color = TRUE) recipes += new/datum/stack_recipe("crude fishing rod", /obj/item/weapon/material/fishing_rod/built, 8, time = 10 SECONDS, pass_stack_color = TRUE) recipes += new/datum/stack_recipe("wooden standup figure", /obj/structure/barricade/cutout, 5, time = 10 SECONDS, pass_stack_color = TRUE) //VOREStation Add + recipes += new/datum/stack_recipe("noticeboard", /obj/structure/noticeboard, 1) /material/wood/log/generate_recipes() recipes = list() diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm index a27e2a3f5f6..02da9be049e 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm @@ -469,7 +469,7 @@ for(var/obj/W in T) //Different classes of items give different commodities. - if(istype(W,/obj/item/weapon/cigbutt)) + if(istype(W,/obj/item/trash/cigbutt)) if(plastic) plastic.add_charge(500) else if(istype(W,/obj/effect/spider/spiderling)) diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot_items.dm index 78d741b0125..41a47b0e7e0 100644 --- a/code/modules/mob/living/silicon/robot/robot_items.dm +++ b/code/modules/mob/living/silicon/robot/robot_items.dm @@ -296,7 +296,7 @@ // Copied over from paper's rename verb // see code\modules\paperwork\paper.dm line 62 -/obj/item/weapon/pen/robopen/proc/RenamePaper(mob/user as mob,obj/paper as obj) +/obj/item/weapon/pen/robopen/proc/RenamePaper(mob/user, obj/item/weapon/paper/paper) if ( !user || !paper ) return var/n_name = sanitizeSafe(input(user, "What would you like to label the paper?", "Paper Labelling", null) as text, 32) @@ -306,6 +306,7 @@ //n_name = copytext(n_name, 1, 32) if(( get_dist(user,paper) <= 1 && user.stat == 0)) paper.name = "paper[(n_name ? text("- '[n_name]'") : null)]" + paper.last_modified_ckey = user.ckey add_fingerprint(user) return diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/phazon.dmglass.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/phazon.dmglass.dm new file mode 100644 index 00000000000..e69de29bb2d diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 8b50d0204f3..5618257494c 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -32,6 +32,8 @@ var/list/offset_y[0] //usage by the photocopier var/rigged = 0 var/spam_flag = 0 + var/age = 0 + var/last_modified_ckey var/const/deffont = "Verdana" var/const/signfont = "Times New Roman" @@ -231,6 +233,15 @@ H.lip_style = null H.update_icons_body() +/obj/item/weapon/paper/proc/set_content(text,title) + if(title) + name = title + info = html_encode(text) + info = parsepencode(text) + update_icon() + update_space(info) + updateinfolinks() + /obj/item/weapon/paper/proc/addtofield(var/id, var/text, var/links = 0) var/locid = 0 var/laststart = 1 @@ -465,6 +476,8 @@ info += t // Oh, he wants to edit to the end of the file, let him. updateinfolinks() + last_modified_ckey = usr.ckey + update_space(t) usr << browse("[name][info_links][stamps]", "window=[name]") // Update the window diff --git a/code/modules/paperwork/paper_sticky.dm b/code/modules/paperwork/paper_sticky.dm new file mode 100644 index 00000000000..e2904aba374 --- /dev/null +++ b/code/modules/paperwork/paper_sticky.dm @@ -0,0 +1,137 @@ +/obj/item/sticky_pad + name = "sticky note pad" + desc = "A pad of densely packed sticky notes." + color = COLOR_YELLOW + icon = 'icons/obj/stickynotes.dmi' + icon_state = "pad_full" + item_state = "paper" + w_class = ITEMSIZE_SMALL + + var/papers = 50 + var/written_text + var/written_by + var/paper_type = /obj/item/weapon/paper/sticky + +/obj/item/sticky_pad/update_icon() + if(papers <= 15) + icon_state = "pad_empty" + else if(papers <= 50) + icon_state = "pad_used" + else + icon_state = "pad_full" + if(written_text) + icon_state = "[icon_state]_writing" + +/obj/item/sticky_pad/attackby(var/obj/item/weapon/thing, var/mob/user) + if(istype(thing, /obj/item/weapon/pen)) + + if(jobban_isbanned(user, "Graffiti")) + to_chat(user, SPAN_WARNING("You are banned from leaving persistent information across rounds.")) + return + + var/writing_space = MAX_MESSAGE_LEN - length(written_text) + if(writing_space <= 0) + to_chat(user, SPAN_WARNING("There is no room left on \the [src].")) + return + var/text = sanitizeSafe(input("What would you like to write?") as text, writing_space) + if(!text || thing.loc != user || (!Adjacent(user) && loc != user) || user.incapacitated()) + return + user.visible_message(SPAN_NOTICE("\The [user] jots a note down on \the [src].")) + written_by = user.ckey + if(written_text) + written_text = "[written_text] [text]" + else + written_text = text + update_icon() + return + ..() + +/obj/item/sticky_pad/examine(var/mob/user) + . = ..() + if(.) + to_chat(user, SPAN_NOTICE("It has [papers] sticky note\s left.")) + to_chat(user, SPAN_NOTICE("You can click it on grab intent to pick it up.")) + +/obj/item/sticky_pad/attack_hand(var/mob/user) + if(user.a_intent == I_GRAB) + ..() + else + var/obj/item/weapon/paper/paper = new paper_type(get_turf(src)) + paper.set_content(written_text, "sticky note") + paper.last_modified_ckey = written_by + paper.color = color + written_text = null + user.put_in_hands(paper) + to_chat(user, SPAN_NOTICE("You pull \the [paper] off \the [src].")) + papers-- + if(papers <= 0) + qdel(src) + else + update_icon() + +/obj/item/sticky_pad/random/Initialize() + . = ..() + color = pick(COLOR_YELLOW, COLOR_LIME, COLOR_CYAN, COLOR_ORANGE, COLOR_PINK) + +/obj/item/weapon/paper/sticky + name = "sticky note" + desc = "Note to self: buy more sticky notes." + icon = 'icons/obj/stickynotes.dmi' + color = COLOR_YELLOW + slot_flags = 0 + +/obj/item/weapon/paper/sticky/Initialize() + . = ..() + GLOB.moved_event.register(src, src, /obj/item/weapon/paper/sticky/proc/reset_persistence_tracking) + +/obj/item/weapon/paper/sticky/proc/reset_persistence_tracking() + SSpersistence.forget_value(src, /datum/persistent/paper/sticky) + pixel_x = 0 + pixel_y = 0 + +/obj/item/weapon/paper/sticky/Destroy() + reset_persistence_tracking() + GLOB.moved_event.unregister(src, src) + . = ..() + +/obj/item/weapon/paper/sticky/update_icon() + if(icon_state != "scrap") + icon_state = info ? "paper_words" : "paper" + +// Copied from duct tape. +/obj/item/weapon/paper/sticky/attack_hand() + . = ..() + if(!istype(loc, /turf)) + reset_persistence_tracking() + +/obj/item/weapon/paper/sticky/afterattack(var/A, var/mob/user, var/flag, var/params) + + if(!in_range(user, A) || istype(A, /obj/machinery/door) || icon_state == "scrap") + return + + var/turf/target_turf = get_turf(A) + var/turf/source_turf = get_turf(user) + + var/dir_offset = 0 + if(target_turf != source_turf) + dir_offset = get_dir(source_turf, target_turf) + if(!(dir_offset in GLOB.cardinal)) + to_chat(user, SPAN_WARNING("You cannot reach that from here.")) + return + + if(user.unEquip(src, source_turf)) + SSpersistence.track_value(src, /datum/persistent/paper/sticky) + if(params) + var/list/mouse_control = params2list(params) + if(mouse_control["icon-x"]) + pixel_x = text2num(mouse_control["icon-x"]) - 16 + if(dir_offset & EAST) + pixel_x += 32 + else if(dir_offset & WEST) + pixel_x -= 32 + if(mouse_control["icon-y"]) + pixel_y = text2num(mouse_control["icon-y"]) - 16 + if(dir_offset & NORTH) + pixel_y += 32 + else if(dir_offset & SOUTH) + pixel_y -= 32 \ No newline at end of file diff --git a/code/modules/persistence/datum/datum_filth.dm b/code/modules/persistence/datum/datum_filth.dm new file mode 100644 index 00000000000..f41fffcfb7f --- /dev/null +++ b/code/modules/persistence/datum/datum_filth.dm @@ -0,0 +1,35 @@ +/datum/persistent/filth + name = "filth" + tokens_per_line = 5 + entries_expire_at = 5 + +/datum/persistent/filth/LabelTokens(var/list/tokens) + var/list/labelled_tokens = ..() + labelled_tokens["path"] = text2path(tokens[LAZYLEN(labelled_tokens)+1]) + return labelled_tokens + +/datum/persistent/filth/IsValidEntry(var/atom/entry) + . = ..() && entry.invisibility == 0 + +/datum/persistent/filth/CheckTokenSanity(var/list/tokens) + return ..() && ispath(tokens["path"]) + +/datum/persistent/filth/CheckTurfContents(var/turf/T, var/list/tokens) + var/_path = tokens["path"] + return (locate(_path) in T) ? FALSE : TRUE + +/datum/persistent/filth/CreateEntryInstance(var/turf/creating, var/list/tokens) + var/_path = tokens["path"] + new _path(creating, tokens["age"]+1) + +/datum/persistent/filth/GetEntryAge(var/atom/entry) + var/obj/effect/decal/cleanable/filth = entry + return filth.age + +/datum/persistent/filth/proc/GetEntryPath(var/atom/entry) + var/obj/effect/decal/cleanable/filth = entry + return filth.generic_filth ? /obj/effect/decal/cleanable/filth : filth.type + +/datum/persistent/filth/CompileEntry(var/atom/entry) + . = ..() + LAZYADD(., "[GetEntryPath(entry)]") \ No newline at end of file diff --git a/code/modules/persistence/datum/datum_graffiti.dm b/code/modules/persistence/datum/datum_graffiti.dm new file mode 100644 index 00000000000..b70b7f94751 --- /dev/null +++ b/code/modules/persistence/datum/datum_graffiti.dm @@ -0,0 +1,51 @@ +/datum/persistent/graffiti + name = "graffiti" + tokens_per_line = 6 + entries_expire_at = 50 + has_admin_data = TRUE + +/datum/persistent/graffiti/LabelTokens(var/list/tokens) + var/list/labelled_tokens = ..() + var/entries = LAZYLEN(labelled_tokens) + labelled_tokens["author"] = tokens[entries+1] + labelled_tokens["message"] = tokens[entries+2] + return labelled_tokens + +/datum/persistent/graffiti/GetValidTurf(var/turf/T, var/list/tokens) + var/turf/checking_turf = ..() + if(istype(checking_turf) && checking_turf.can_engrave()) + return checking_turf + +/datum/persistent/graffiti/CheckTurfContents(var/turf/T, var/list/tokens) + var/too_much_graffiti = 0 + for(var/obj/effect/decal/writing/W in .) + too_much_graffiti++ + if(too_much_graffiti >= 5) + return FALSE + return TRUE + +/datum/persistent/graffiti/CreateEntryInstance(var/turf/creating, var/list/tokens) + new /obj/effect/decal/writing(creating, tokens["age"]+1, tokens["message"], tokens["author"]) + +/datum/persistent/graffiti/IsValidEntry(var/atom/entry) + . = ..() + if(.) + var/turf/T = entry.loc + . = T.can_engrave() + +/datum/persistent/graffiti/GetEntryAge(var/atom/entry) + var/obj/effect/decal/writing/save_graffiti = entry + return save_graffiti.graffiti_age + +/datum/persistent/graffiti/CompileEntry(var/atom/entry, var/write_file) + . = ..() + var/obj/effect/decal/writing/save_graffiti = entry + LAZYADD(., "[save_graffiti.author ? save_graffiti.author : "unknown"]") + LAZYADD(., "[save_graffiti.message]") + +/datum/persistent/graffiti/GetAdminDataStringFor(var/thing, var/can_modify, var/mob/user) + var/obj/effect/decal/writing/save_graffiti = thing + if(can_modify) + . = "[save_graffiti.message][save_graffiti.author]Destroy" + else + . = "[save_graffiti.message][save_graffiti.author]" \ No newline at end of file diff --git a/code/modules/persistence/datum/datum_paper.dm b/code/modules/persistence/datum/datum_paper.dm new file mode 100644 index 00000000000..e572faea013 --- /dev/null +++ b/code/modules/persistence/datum/datum_paper.dm @@ -0,0 +1,56 @@ +/datum/persistent/paper + name = "paper" + tokens_per_line = 7 + entries_expire_at = 50 + has_admin_data = TRUE + var/paper_type = /obj/item/weapon/paper + var/requires_noticeboard = TRUE + +/datum/persistent/paper/LabelTokens(var/list/tokens) + var/list/labelled_tokens = ..() + var/entries = LAZYLEN(labelled_tokens) + labelled_tokens["author"] = tokens[entries+1] + labelled_tokens["message"] = tokens[entries+2] + labelled_tokens["title"] = tokens[entries+3] + return labelled_tokens + +/datum/persistent/paper/CheckTurfContents(var/turf/T, var/list/tokens) + if(requires_noticeboard && !(locate(/obj/structure/noticeboard) in T)) + new /obj/structure/noticeboard(T) + . = ..() + +/datum/persistent/paper/CreateEntryInstance(var/turf/creating, var/list/tokens) + var/obj/structure/noticeboard/board = locate() in creating + if(requires_noticeboard && LAZYLEN(board.notices) >= board.max_notices) + return + var/obj/item/weapon/paper/paper = new paper_type(creating) + paper.set_content(tokens["message"], tokens["title"]) + paper.last_modified_ckey = tokens["author"] + if(requires_noticeboard) + board.add_paper(paper) + SSpersistence.track_value(paper, type) + return paper + +/datum/persistent/paper/GetEntryAge(var/atom/entry) + var/obj/item/weapon/paper/paper = entry + return paper.age + +/datum/persistent/paper/CompileEntry(var/atom/entry, var/write_file) + . = ..() + var/obj/item/weapon/paper/paper = entry + LAZYADD(., "[paper.last_modified_ckey ? paper.last_modified_ckey : "unknown"]") + LAZYADD(., "[paper.info]") + LAZYADD(., "[paper.name]") + +/datum/persistent/paper/GetAdminDataStringFor(var/thing, var/can_modify, var/mob/user) + var/obj/item/weapon/paper/paper = thing + if(can_modify) + . = "[paper.info][paper.name][paper.last_modified_ckey]Destroy" + else + . = "[paper.info][paper.name][paper.last_modified_ckey]" + +/datum/persistent/paper/RemoveValue(var/atom/value) + var/obj/structure/noticeboard/board = value.loc + if(istype(board)) + board.remove_paper(value) + qdel(value) \ No newline at end of file diff --git a/code/modules/persistence/datum/datum_paper_sticky.dm b/code/modules/persistence/datum/datum_paper_sticky.dm new file mode 100644 index 00000000000..1871eb4a037 --- /dev/null +++ b/code/modules/persistence/datum/datum_paper_sticky.dm @@ -0,0 +1,28 @@ +/datum/persistent/paper/sticky + name = "stickynotes" + paper_type = /obj/item/weapon/paper/sticky + requires_noticeboard = FALSE + tokens_per_line = 10 + +/datum/persistent/paper/sticky/LabelTokens(var/list/tokens) + var/list/labelled_tokens = ..() + var/entries = LAZYLEN(labelled_tokens) + labelled_tokens["offset_x"] = tokens[entries+1] + labelled_tokens["offset_y"] = tokens[entries+2] + labelled_tokens["color"] = tokens[entries+3] + return labelled_tokens + +/datum/persistent/paper/sticky/CreateEntryInstance(var/turf/creating, var/list/tokens) + var/atom/paper = ..() + if(paper) + paper.pixel_x = text2num(tokens["offset_x"]) + paper.pixel_y = text2num(tokens["offset_y"]) + paper.color = tokens["color"] + return paper + +/datum/persistent/paper/sticky/CompileEntry(var/atom/entry, var/write_file) + . = ..() + var/obj/item/weapon/paper/sticky/paper = entry + LAZYADD(., "[paper.pixel_x]") + LAZYADD(., "[paper.pixel_y]") + LAZYADD(., "[paper.color]") \ No newline at end of file diff --git a/code/modules/persistence/datum/datum_trash.dm b/code/modules/persistence/datum/datum_trash.dm new file mode 100644 index 00000000000..951e9858af8 --- /dev/null +++ b/code/modules/persistence/datum/datum_trash.dm @@ -0,0 +1,17 @@ +/datum/persistent/filth/trash + name = "trash" + +/datum/persistent/filth/trash/CheckTurfContents(var/turf/T, var/list/tokens) + var/too_much_trash = 0 + for(var/obj/item/trash/trash in T) + too_much_trash++ + if(too_much_trash >= 5) + return FALSE + return TRUE + +/datum/persistent/filth/trash/GetEntryAge(var/atom/entry) + var/obj/item/trash/trash = entry + return trash.age + +/datum/persistent/filth/trash/GetEntryPath(var/atom/entry) + return entry.type \ No newline at end of file diff --git a/code/modules/persistence/datum/persistence_datum.dm b/code/modules/persistence/datum/persistence_datum.dm new file mode 100644 index 00000000000..8a409404cd6 --- /dev/null +++ b/code/modules/persistence/datum/persistence_datum.dm @@ -0,0 +1,160 @@ +// This is a set of datums instantiated by SSpersistence. +// They basically just handle loading, processing and saving specific forms +// of persistent data like graffiti and round to round filth. + +/datum/persistent + var/name + var/filename + var/tokens_per_line + var/entries_expire_at + var/entries_decay_at + var/entry_decay_weight = 0.5 + var/file_entry_split_character = "\t" + var/file_entry_substitute_character = " " + var/file_line_split_character = "\n" + var/has_admin_data + +/datum/persistent/New() + SetFilename() + ..() + +/datum/persistent/proc/SetFilename() + if(name) + filename = "data/persistent/[lowertext(using_map.name)]-[lowertext(name)].txt" + if(!isnull(entries_decay_at) && !isnull(entries_expire_at)) + entries_decay_at = round(entries_expire_at * entries_decay_at) + +/datum/persistent/proc/LabelTokens(var/list/tokens) + var/list/labelled_tokens = list() + labelled_tokens["x"] = text2num(tokens[1]) + labelled_tokens["y"] = text2num(tokens[2]) + labelled_tokens["z"] = text2num(tokens[3]) + labelled_tokens["age"] = text2num(tokens[4]) + return labelled_tokens + +/datum/persistent/proc/GetValidTurf(var/turf/T, var/list/tokens) + if(T && CheckTurfContents(T, tokens)) + return T + +/datum/persistent/proc/CheckTurfContents(var/turf/T, var/list/tokens) + return TRUE + +/datum/persistent/proc/CheckTokenSanity(var/list/tokens) + return ( \ + !isnull(tokens["x"]) && \ + !isnull(tokens["y"]) && \ + !isnull(tokens["z"]) && \ + !isnull(tokens["age"]) && \ + tokens["age"] <= entries_expire_at \ + ) + +/datum/persistent/proc/CreateEntryInstance(var/turf/creating, var/list/tokens) + return + +/datum/persistent/proc/ProcessAndApplyTokens(var/list/tokens) + + // If it's old enough we start to trim down any textual information and scramble strings. + if(tokens["message"] && !isnull(entries_decay_at) && !isnull(entry_decay_weight)) + var/_n = tokens["age"] + var/_message = tokens["message"] + if(_n >= entries_decay_at) + var/decayed_message = "" + for(var/i = 1 to length(_message)) + var/char = copytext(_message, i, i + 1) + if(prob(round(_n * entry_decay_weight))) + if(prob(99)) + decayed_message += pick(".",",","-","'","\\","/","\"",":",";") + else + decayed_message += char + _message = decayed_message + if(length(_message)) + tokens["message"] = _message + else + return + + var/_z = tokens["z"] + if(_z in using_map.station_levels) + . = GetValidTurf(locate(tokens["x"], tokens["y"], _z), tokens) + if(.) + CreateEntryInstance(., tokens) + +/datum/persistent/proc/IsValidEntry(var/atom/entry) + if(!istype(entry)) + return FALSE + if(GetEntryAge(entry) >= entries_expire_at) + return FALSE + var/turf/T = get_turf(entry) + if(!T || !(T.z in using_map.station_levels) ) + return FALSE + var/area/A = get_area(T) + if(!A || (A.flags & AREA_FLAG_IS_NOT_PERSISTENT)) + return FALSE + return TRUE + +/datum/persistent/proc/GetEntryAge(var/atom/entry) + return 0 + +/datum/persistent/proc/CompileEntry(var/atom/entry) + var/turf/T = get_turf(entry) + . = list( + T.x, + T.y, + T.z, + GetEntryAge(entry) + ) + +/datum/persistent/proc/Initialize() + if(fexists(filename)) + for(var/entry_line in file2list(filename, file_line_split_character)) + if(!entry_line) + continue + var/list/tokens = splittext(entry_line, file_entry_split_character) + if(LAZYLEN(tokens) < tokens_per_line) + continue + tokens = LabelTokens(tokens) + if(!CheckTokenSanity(tokens)) + continue + ProcessAndApplyTokens(tokens) + +/datum/persistent/proc/Shutdown() + if(fexists(filename)) + fdel(filename) + var/write_file = file(filename) + for(var/thing in SSpersistence.tracking_values[type]) + if(IsValidEntry(thing)) + var/list/entry = CompileEntry(thing) + if(LAZYLEN(entry) == tokens_per_line) + for(var/i = 1 to LAZYLEN(entry)) + if(istext(entry[i])) + entry[i] = replacetext(entry[i], file_entry_split_character, file_entry_substitute_character) + to_file(write_file, jointext(entry, file_entry_split_character)) + +/datum/persistent/proc/RemoveValue(var/atom/value) + qdel(value) + +/datum/persistent/proc/GetAdminSummary(var/mob/user, var/can_modify) + . = list("[capitalize(name)]") + . += "
" + for(var/thing in SSpersistence.tracking_values[type]) + . += "[GetAdminDataStringFor(thing, can_modify, user)]" + . += "
" + + +/datum/persistent/proc/GetAdminDataStringFor(var/thing, var/can_modify, var/mob/user) + if(can_modify) + . = "[thing]Destroy" + else + . = "[thing]" + +/datum/persistent/Topic(var/href, var/href_list) + . = ..() + if(!.) + if(href_list["remove_entry"]) + var/datum/value = locate(href_list["remove_entry"]) + if(istype(value)) + RemoveValue(value) + . = TRUE + if(.) + var/mob/user = locate(href_list["caller"]) + if(user) + SSpersistence.show_info(user) \ No newline at end of file diff --git a/code/modules/persistence/filth.dm b/code/modules/persistence/filth.dm new file mode 100644 index 00000000000..2eea824f83f --- /dev/null +++ b/code/modules/persistence/filth.dm @@ -0,0 +1,12 @@ +/obj/effect/decal/cleanable/filth + name = "filth" + desc = "Disgusting. Someone from last shift didn't do their job properly." + icon = 'icons/effects/blood.dmi' + icon_state = "mfloor1" + random_icon_states = list("mfloor1", "mfloor2", "mfloor3", "mfloor4", "mfloor5", "mfloor6", "mfloor7") + color = "#464f33" + persistent = TRUE + +/obj/effect/decal/cleanable/filth/Initialize() + . = ..() + alpha = rand(180,220) \ No newline at end of file diff --git a/code/modules/persistence/graffiti.dm b/code/modules/persistence/graffiti.dm new file mode 100644 index 00000000000..d6a56b75f17 --- /dev/null +++ b/code/modules/persistence/graffiti.dm @@ -0,0 +1,64 @@ +/obj/effect/decal/writing + name = "hand graffiti" + icon_state = "writing1" + icon = 'icons/effects/writing.dmi' + desc = "It looks like someone has scratched something here." + plane = DIRTY_PLANE + gender = PLURAL + blend_mode = BLEND_MULTIPLY + color = "#000000" + alpha = 120 + + var/message + var/graffiti_age = 0 + var/author = "unknown" + +/obj/effect/decal/writing/New(var/newloc, var/_age, var/_message, var/_author) + ..(newloc) + if(!isnull(_age)) + graffiti_age = _age + message = _message + if(!isnull(author)) + author = _author + +/obj/effect/decal/writing/Initialize() + var/list/random_icon_states = icon_states(icon) + for(var/obj/effect/decal/writing/W in loc) + random_icon_states.Remove(W.icon_state) + if(random_icon_states.len) + icon_state = pick(random_icon_states) + SSpersistence.track_value(src, /datum/persistent/graffiti) + . = ..() + +/obj/effect/decal/writing/Destroy() + SSpersistence.forget_value(src, /datum/persistent/graffiti) + . = ..() + +/obj/effect/decal/writing/examine(mob/user) + . = ..() + to_chat(user, "It reads \"[message]\".") + +/obj/effect/decal/writing/attackby(var/obj/item/thing, var/mob/user) + if(is_hot(thing)) + var/obj/item/weapon/weldingtool/welder = thing + if(welder.isOn() && welder.remove_fuel(0,user) && do_after(user, 5, src) && !QDELETED(src)) + playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1) + user.visible_message("\The [user] clears away some graffiti.") + qdel(src) + else if(thing.sharp) + + if(jobban_isbanned(user, "Graffiti")) + to_chat(user, SPAN_WARNING("You are banned from leaving persistent information across rounds.")) + return + + var/_message = sanitize(input("Enter an additional message to engrave.", "Graffiti") as null|text, trim = TRUE) + if(_message && loc && user && !user.incapacitated() && user.Adjacent(loc) && thing.loc == user) + user.visible_message("\The [user] begins carving something into \the [loc].") + if(do_after(user, max(20, length(_message)), src) && loc) + user.visible_message("\The [user] carves some graffiti into \the [loc].") + message = "[message] [_message]" + author = user.ckey + if(lowertext(message) == "elbereth") + to_chat(user, "You feel much safer.") + else + . = ..() \ No newline at end of file diff --git a/code/modules/persistence/noticeboard.dm b/code/modules/persistence/noticeboard.dm new file mode 100644 index 00000000000..ad8cfef590b --- /dev/null +++ b/code/modules/persistence/noticeboard.dm @@ -0,0 +1,221 @@ +/obj/structure/noticeboard + name = "notice board" + desc = "A board for pinning important notices upon." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "nboard00" + density = 0 + anchored = 1 + var/list/notices + var/base_icon_state = "nboard0" + var/const/max_notices = 5 + +/obj/structure/noticeboard/Initialize() + . = ..() + + // Grab any mapped notices. + notices = list() + for(var/obj/item/weapon/paper/note in get_turf(src)) + note.forceMove(src) + LAZYADD(notices, note) + if(LAZYLEN(notices) >= max_notices) + break + + // Automatically place noticeboards that aren't mapped to specific positions. + if(pixel_x == 0 && pixel_y == 0) + + var/turf/here = get_turf(src) + var/placing = 0 + for(var/checkdir in GLOB.cardinal) + var/turf/T = get_step(here, checkdir) + if(T.density) + placing = checkdir + break + for(var/thing in T) + var/atom/A = thing + if(A.simulated && !A.CanPass(src, T)) + placing = checkdir + break + + switch(placing) + if(NORTH) + pixel_x = 0 + pixel_y = 32 + if(SOUTH) + pixel_x = 0 + pixel_y = -32 + if(EAST) + pixel_x = 32 + pixel_y = 0 + if(WEST) + pixel_x = -32 + pixel_y = 0 + + update_icon() + +/obj/structure/noticeboard/proc/add_paper(var/atom/movable/paper, var/skip_icon_update) + if(istype(paper)) + LAZYDISTINCTADD(notices, paper) + paper.forceMove(src) + if(!skip_icon_update) + update_icon() + +/obj/structure/noticeboard/proc/remove_paper(var/atom/movable/paper, var/skip_icon_update) + if(istype(paper) && paper.loc == src) + paper.dropInto(loc) + LAZYREMOVE(notices, paper) + SSpersistence.forget_value(paper, /datum/persistent/paper) + if(!skip_icon_update) + update_icon() + +/obj/structure/noticeboard/proc/dismantle() + for(var/thing in notices) + remove_paper(thing, skip_icon_update = TRUE) + new /obj/item/stack/material/wood(get_turf(src)) + qdel(src) + +/obj/structure/noticeboard/Destroy() + QDEL_NULL_LIST(notices) + . = ..() + +/obj/structure/noticeboard/ex_act(var/severity) + dismantle() + +/obj/structure/noticeboard/update_icon() + icon_state = "[base_icon_state][LAZYLEN(notices)]" + +/obj/structure/noticeboard/attackby(var/obj/item/weapon/thing, var/mob/user) + if(thing.is_screwdriver()) + var/choice = input("Which direction do you wish to place the noticeboard?", "Noticeboard Offset") as null|anything in list("North", "South", "East", "West") + if(choice && Adjacent(user) && thing.loc == user && !user.incapacitated()) + playsound(loc, 'sound/items/Screwdriver.ogg', 50, 1) + switch(choice) + if("North") + pixel_x = 0 + pixel_y = 32 + if("South") + pixel_x = 0 + pixel_y = -32 + if("East") + pixel_x = 32 + pixel_y = 0 + if("West") + pixel_x = -32 + pixel_y = 0 + return + else if(thing.is_wrench()) + visible_message(SPAN_WARNING("\The [user] begins dismantling \the [src].")) + playsound(loc, 'sound/items/Ratchet.ogg', 50, 1) + if(do_after(user, 50, src)) + visible_message(SPAN_DANGER("\The [user] has dismantled \the [src]!")) + dismantle() + return + else if(istype(thing, /obj/item/weapon/paper) || istype(thing, /obj/item/weapon/photo)) + if(jobban_isbanned(user, "Graffiti")) + to_chat(user, SPAN_WARNING("You are banned from leaving persistent information across rounds.")) + else + if(LAZYLEN(notices) < max_notices && user.unEquip(thing, src)) + add_fingerprint(user) + add_paper(thing) + to_chat(user, SPAN_NOTICE("You pin \the [thing] to \the [src].")) + SSpersistence.track_value(thing, /datum/persistent/paper) + else + to_chat(user, SPAN_WARNING("You hesitate, certain \the [thing] will not be seen among the many others already attached to \the [src].")) + return + ..() + +/obj/structure/noticeboard/attack_ai(var/mob/user) + examine(user) + +/obj/structure/noticeboard/attack_hand(var/mob/user) + examine(user) + +/obj/structure/noticeboard/examine(var/mob/user) + . = ..() + if(.) + var/list/dat = list("") + for(var/thing in notices) + LAZYADD(dat, "") + var/datum/browser/popup = new(user, "noticeboard-\ref[src]", "Noticeboard") + popup.set_content(jointext(dat, null)) + popup.open() + +/obj/structure/noticeboard/Topic(var/mob/user, var/list/href_list) + if(href_list["read"]) + var/obj/item/weapon/paper/P = locate(href_list["read"]) + if(P && P.loc == src) + P.show_content(user) + . = TOPIC_HANDLED + + if(href_list["look"]) + var/obj/item/weapon/photo/P = locate(href_list["look"]) + if(P && P.loc == src) + P.show(user) + . = TOPIC_HANDLED + + if(href_list["remove"]) + remove_paper(locate(href_list["remove"])) + add_fingerprint(user) + . = TOPIC_REFRESH + + if(href_list["write"]) + if((usr.stat || usr.restrained())) //For when a player is handcuffed while they have the notice window open + return + var/obj/item/P = locate(href_list["write"]) + if((P && P.loc == src)) //ifthe paper's on the board + var/mob/living/M = usr + if(istype(M)) + var/obj/item/weapon/pen/E = M.get_type_in_hands(/obj/item/weapon/pen) + if(E) + add_fingerprint(M) + P.attackby(E, usr) + else + to_chat(M, "You'll need something to write with!") + . = TOPIC_REFRESH + + if(. == TOPIC_REFRESH) + interact(user) + +/obj/structure/noticeboard/anomaly + notices = 5 + icon_state = "nboard05" + +/obj/structure/noticeboard/anomaly/New() + var/obj/item/weapon/paper/P = new() + P.name = "Memo RE: proper analysis procedure" + P.info = "
We keep test dummies in pens here for a reason, so standard procedure should be to activate newfound alien artifacts and place the two in close proximity. Promising items I might even approve monkey testing on." + P.stamped = list(/obj/item/weapon/stamp/rd) + P.overlays = list("paper_stamped_rd") + src.contents += P + + P = new() + P.name = "Memo RE: materials gathering" + P.info = "Corasang,
the hands-on approach to gathering our samples may very well be slow at times, but it's safer than allowing the blundering miners to roll willy-nilly over our dig sites in their mechs, destroying everything in the process. And don't forget the escavation tools on your way out there!
- R.W" + P.stamped = list(/obj/item/weapon/stamp/rd) + P.overlays = list("paper_stamped_rd") + src.contents += P + + P = new() + P.name = "Memo RE: ethical quandaries" + P.info = "Darion-

I don't care what his rank is, our business is that of science and knowledge - questions of moral application do not come into this. Sure, so there are those who would employ the energy-wave particles my modified device has managed to abscond for their own personal gain, but I can hardly see the practical benefits of some of these artifacts our benefactors left behind. Ward--" + P.stamped = list(/obj/item/weapon/stamp/rd) + P.overlays = list("paper_stamped_rd") + src.contents += P + + P = new() + P.name = "READ ME! Before you people destroy any more samples" + P.info = "how many times do i have to tell you people, these xeno-arch samples are del-i-cate, and should be handled so! careful application of a focussed, concentrated heat or some corrosive liquids should clear away the extraneous carbon matter, while application of an energy beam will most decidedly destroy it entirely - like someone did to the chemical dispenser! W, the one who signs your paychecks" + P.stamped = list(/obj/item/weapon/stamp/rd) + P.overlays = list("paper_stamped_rd") + src.contents += P + + P = new() + P.name = "Reminder regarding the anomalous material suits" + P.info = "Do you people think the anomaly suits are cheap to come by? I'm about a hair trigger away from instituting a log book for the damn things. Only wear them if you're going out for a dig, and for god's sake don't go tramping around in them unless you're field testing something, R" + P.stamped = list(/obj/item/weapon/stamp/rd) + P.overlays = list("paper_stamped_rd") + src.contents += P \ No newline at end of file diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index cf11871d1af..82030314a0f 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -640,7 +640,7 @@ to_chat(src, "You can taste the flavor of spicy cardboard.") else if(istype(I,/obj/item/device/flashlight/glowstick)) to_chat(src, "You found out the glowy juice only tastes like regret.") - else if(istype(I,/obj/item/weapon/cigbutt)) + else if(istype(I,/obj/item/trash/cigbutt)) to_chat(src, "You can taste the flavor of bitter ash. Classy.") else if(istype(I,/obj/item/clothing/mask/smokable)) var/obj/item/clothing/mask/smokable/C = I diff --git a/code/modules/xenoarcheaology/misc.dm b/code/modules/xenoarcheaology/misc.dm index f6005c3f172..3ef15cbc3c5 100644 --- a/code/modules/xenoarcheaology/misc.dm +++ b/code/modules/xenoarcheaology/misc.dm @@ -1,43 +1,3 @@ -/obj/structure/noticeboard/anomaly - notices = 5 - icon_state = "nboard05" - -/obj/structure/noticeboard/anomaly/New() - var/obj/item/weapon/paper/P = new() - P.name = "Memo RE: proper analysis procedure" - P.info = "
We keep test dummies in pens here for a reason, so standard procedure should be to activate newfound alien artifacts and place the two in close proximity. Promising items I might even approve monkey testing on." - P.stamped = list(/obj/item/weapon/stamp/rd) - P.overlays = list("paper_stamped_rd") - src.contents += P - - P = new() - P.name = "Memo RE: materials gathering" - P.info = "Corasang,
the hands-on approach to gathering our samples may very well be slow at times, but it's safer than allowing the blundering miners to roll willy-nilly over our dig sites in their mechs, destroying everything in the process. And don't forget the escavation tools on your way out there!
- R.W" - P.stamped = list(/obj/item/weapon/stamp/rd) - P.overlays = list("paper_stamped_rd") - src.contents += P - - P = new() - P.name = "Memo RE: ethical quandaries" - P.info = "Darion-

I don't care what his rank is, our business is that of science and knowledge - questions of moral application do not come into this. Sure, so there are those who would employ the energy-wave particles my modified device has managed to abscond for their own personal gain, but I can hardly see the practical benefits of some of these artifacts our benefactors left behind. Ward--" - P.stamped = list(/obj/item/weapon/stamp/rd) - P.overlays = list("paper_stamped_rd") - src.contents += P - - P = new() - P.name = "READ ME! Before you people destroy any more samples" - P.info = "how many times do i have to tell you people, these xeno-arch samples are del-i-cate, and should be handled so! careful application of a focussed, concentrated heat or some corrosive liquids should clear away the extraneous carbon matter, while application of an energy beam will most decidedly destroy it entirely - like someone did to the chemical dispenser! W, the one who signs your paychecks" - P.stamped = list(/obj/item/weapon/stamp/rd) - P.overlays = list("paper_stamped_rd") - src.contents += P - - P = new() - P.name = "Reminder regarding the anomalous material suits" - P.info = "Do you people think the anomaly suits are cheap to come by? I'm about a hair trigger away from instituting a log book for the damn things. Only wear them if you're going out for a dig, and for god's sake don't go tramping around in them unless you're field testing something, R" - P.stamped = list(/obj/item/weapon/stamp/rd) - P.overlays = list("paper_stamped_rd") - src.contents += P - /obj/structure/bookcase/manuals/xenoarchaeology name = "Xenoarchaeology Manuals bookcase" diff --git a/html/changelogs/cerebulon - persistence.yml b/html/changelogs/cerebulon - persistence.yml new file mode 100644 index 00000000000..3b12f419672 --- /dev/null +++ b/html/changelogs/cerebulon - persistence.yml @@ -0,0 +1,38 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +################################# + +# Your name. +author: Cerebulon + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added sticky notes, orderable from cargo" + -rscadd: "You can now carve graffiti into suitable walls/floors with sharp objects." + - rscadd: "Trash, graffiti, dirt (Not blood), noticeboards and stickynotes are now persistent across rounds. This is admin-toggleable." diff --git a/icons/effects/writing.dmi b/icons/effects/writing.dmi new file mode 100644 index 00000000000..bbf4055bcea Binary files /dev/null and b/icons/effects/writing.dmi differ diff --git a/icons/obj/stickynotes.dmi b/icons/obj/stickynotes.dmi new file mode 100644 index 00000000000..8ca70a41493 Binary files /dev/null and b/icons/obj/stickynotes.dmi differ diff --git a/maps/southern_cross/southern_cross-6.dmm b/maps/southern_cross/southern_cross-6.dmm index 7ace6bbf067..b7a4a729f46 100644 --- a/maps/southern_cross/southern_cross-6.dmm +++ b/maps/southern_cross/southern_cross-6.dmm @@ -2174,7 +2174,7 @@ "PP" = (/obj/machinery/light{dir = 8; icon_state = "tube1"; pixel_y = 0},/obj/machinery/computer/station_alert{dir = 4},/turf/simulated/shuttle/floor/voidcraft/light,/area/ninja_dojo/start) "PQ" = (/obj/machinery/light{dir = 4; icon_state = "tube1"; pixel_x = 0},/obj/machinery/computer/security{dir = 8},/turf/simulated/shuttle/floor/voidcraft/light,/area/ninja_dojo/start) "PR" = (/obj/structure/closet/crate/freezer/rations,/turf/simulated/shuttle/floor/voidcraft,/area/syndicate_station/start) -"PS" = (/obj/item/weapon/cigbutt,/turf/simulated/shuttle/floor/voidcraft/dark,/area/syndicate_station/start) +"PS" = (/obj/item/trash/cigbutt,/turf/simulated/shuttle/floor/voidcraft/dark,/area/syndicate_station/start) "PT" = (/obj/machinery/light/small{dir = 1},/turf/simulated/shuttle/floor/voidcraft/dark,/area/syndicate_station/start) "PU" = (/obj/machinery/light{dir = 1},/obj/structure/table/steel,/obj/item/roller,/obj/item/roller,/obj/item/roller,/obj/item/device/defib_kit/compact/combat/loaded,/turf/simulated/shuttle/floor/voidcraft/light,/area/syndicate_station/start) "PV" = (/obj/structure/closet/secure_closet/medical_wall{pixel_y = 32; req_access = list(150)},/obj/item/bodybag,/obj/item/weapon/reagent_containers/syringe/antiviral,/obj/item/weapon/reagent_containers/syringe/antiviral,/obj/item/weapon/reagent_containers/syringe/antiviral,/obj/item/weapon/reagent_containers/glass/bottle/antitoxin{pixel_x = -4; pixel_y = 8},/obj/item/weapon/reagent_containers/glass/bottle/inaprovaline{pixel_x = 4; pixel_y = 7},/obj/item/weapon/reagent_containers/syringe,/obj/item/weapon/storage/firstaid/combat,/obj/item/weapon/storage/firstaid/clotting,/turf/simulated/shuttle/floor/voidcraft/light,/area/syndicate_station/start) diff --git a/maps/southern_cross/southern_cross_areas.dm b/maps/southern_cross/southern_cross_areas.dm index bd7cca429ee..471e70c6f9a 100644 --- a/maps/southern_cross/southern_cross_areas.dm +++ b/maps/southern_cross/southern_cross_areas.dm @@ -60,6 +60,7 @@ /area/surface/outside ambience = AMBIENCE_SIF always_unpowered = TRUE + flags = AREA_FLAG_IS_NOT_PERSISTENT // The area near the outpost, so POIs don't show up right next to the outpost. /area/surface/outside/plains/outpost @@ -123,7 +124,7 @@ /area/surface/cave - flags = RAD_SHIELDED + flags = RAD_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT always_unpowered = TRUE /area/surface/cave @@ -398,7 +399,7 @@ icon_state = "shuttle" requires_power = 0 dynamic_lighting = 1 - flags = RAD_SHIELDED + flags = RAD_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT /area/turbolift/start name = "\improper Turbolift Start" @@ -753,11 +754,13 @@ name = "\improper Command - HoP's Office" icon_state = "head_quarters" holomap_color = HOLOMAP_AREACOLOR_COMMAND + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/crew_quarters/heads/sc/hor name = "\improper Research - RD's Office" icon_state = "head_quarters" holomap_color = HOLOMAP_AREACOLOR_SCIENCE + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/crew_quarters/heads/sc/chief name = "\improper Engineering - CE's Office" @@ -773,6 +776,7 @@ name = "\improper Medbay - CMO's Office" icon_state = "head_quarters" holomap_color = HOLOMAP_AREACOLOR_MEDICAL + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/engineering/engineer_eva name = "\improper Engineering EVA" @@ -1052,6 +1056,7 @@ area/crew_quarters/heads/sc/hop/quarters name = "\improper Third Deck Plating" dynamic_lighting = 0 ambience = AMBIENCE_SPACE + flags = AREA_FLAG_IS_NOT_PERSISTENT // Shuttles diff --git a/maps/submaps/surface_submaps/mountains/deadBeacon.dmm b/maps/submaps/surface_submaps/mountains/deadBeacon.dmm index 2a32fb4f644..253b4c53c9f 100644 --- a/maps/submaps/surface_submaps/mountains/deadBeacon.dmm +++ b/maps/submaps/surface_submaps/mountains/deadBeacon.dmm @@ -27,7 +27,7 @@ "A" = (/obj/item/weapon/circuitboard/broken,/turf/simulated/mineral/floor/ignore_mapgen,/area/submap/cave/deadBeacon) "B" = (/obj/structure/grille/broken,/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/plating,/area/submap/cave/deadBeacon) "C" = (/obj/structure/loot_pile/maint/junk,/turf/simulated/floor/plating,/area/submap/cave/deadBeacon) -"D" = (/obj/item/weapon/cigbutt,/obj/item/weapon/tool/wrench,/obj/machinery/light,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/cave/deadBeacon) +"D" = (/obj/item/trash/cigbutt,/obj/item/weapon/tool/wrench,/obj/machinery/light,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/cave/deadBeacon) "E" = (/obj/item/weapon/material/shard,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/cave/deadBeacon) "F" = (/obj/machinery/recharge_station,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/cave/deadBeacon) "G" = (/obj/structure/grille,/obj/structure/window/reinforced{dir = 4},/obj/structure/window/reinforced{dir = 8},/obj/structure/window/reinforced,/turf/simulated/floor/plating,/area/submap/cave/deadBeacon) diff --git a/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm b/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm index 390589daac0..7da857ef053 100644 --- a/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm +++ b/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm @@ -25,7 +25,7 @@ "ay" = (/obj/effect/decal/remains/xeno,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) "az" = (/obj/item/trash/syndi_cakes,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) "aA" = (/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) -"aB" = (/obj/item/weapon/cigbutt,/obj/item/weapon/tank/emergency/oxygen,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) +"aB" = (/obj/item/trash/cigbutt,/obj/item/weapon/tank/emergency/oxygen,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) "aC" = (/obj/item/weapon/material/knife/tacknife/boot,/obj/item/clothing/mask/breath,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) "aD" = (/obj/effect/decal/remains/human,/turf/simulated/shuttle/floor{icon_state = "floor_white"},/area/submap/cave/qShuttle) "aE" = (/obj/item/trash/sosjerky,/obj/item/weapon/storage/box/donut/empty,/obj/item/weapon/reagent_containers/food/drinks/sillycup,/turf/simulated/shuttle/floor{icon_state = "floor_white"},/area/submap/cave/qShuttle) @@ -50,7 +50,7 @@ "aX" = (/obj/item/device/paicard,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) "aY" = (/obj/machinery/door/blast/regular{name = "Cargo Door"},/obj/item/tape/medical{dir = 4; icon_state = "tape_door_0"; layer = 3.4},/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) "aZ" = (/obj/effect/decal/remains/human,/obj/item/clothing/under/mbill{desc = "A uniform belonging to Major Bill's Transportation, a shipping megacorporation. This looks at least a few decades out of date."; name = "\improper old Major Bill's uniform"},/obj/item/clothing/head/soft/mbill{desc = "It's a ballcap bearing the colors of Major Bill's Shipping. This one looks at least a few decades out of date."; name = "old shipping cap"},/turf/simulated/shuttle/floor,/area/submap/cave/qShuttle) -"ba" = (/obj/item/weapon/cigbutt,/turf/simulated/shuttle/floor{icon_state = "floor_white"},/area/submap/cave/qShuttle) +"ba" = (/obj/item/trash/cigbutt,/turf/simulated/shuttle/floor{icon_state = "floor_white"},/area/submap/cave/qShuttle) "bb" = (/obj/machinery/door/airlock/command{icon_state = "door_locked"; locked = 1},/turf/simulated/shuttle/floor{icon_state = "floor_white"},/area/submap/cave/qShuttle) "bc" = (/obj/item/weapon/tank/emergency/oxygen/engi,/obj/effect/decal/cleanable/dirt,/turf/simulated/shuttle/floor{icon_state = "floor_white"},/area/submap/cave/qShuttle) "bd" = (/obj/effect/decal/remains/human,/obj/item/clothing/suit/space/emergency,/obj/item/clothing/head/helmet/space/emergency,/obj/effect/decal/cleanable/dirt,/turf/simulated/shuttle/floor{icon_state = "floor_yellow"},/area/submap/cave/qShuttle) diff --git a/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm b/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm index 4f41a473f8a..c33096f9f73 100644 --- a/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm +++ b/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm @@ -5,6 +5,7 @@ ambience = AMBIENCE_RUINS secret_name = TRUE forbid_events = TRUE + flags = AREA_FLAG_IS_NOT_PERSISTENT /area/submap/event //To be used for Events not for regular PoIs name = "Unknown" diff --git a/vorestation.dme b/vorestation.dme index 4416b3ccca5..30d0ae0f022 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -257,6 +257,7 @@ #include "code\controllers\subsystems\orbits.dm" #include "code\controllers\subsystems\overlays.dm" #include "code\controllers\subsystems\persist_vr.dm" +#include "code\controllers\subsystems\persistence.dm" #include "code\controllers\subsystems\planets.dm" #include "code\controllers\subsystems\radiation.dm" #include "code\controllers\subsystems\shuttles.dm" @@ -1388,7 +1389,6 @@ #include "code\game\objects\structures\morgue.dm" #include "code\game\objects\structures\morgue_vr.dm" #include "code\game\objects\structures\musician.dm" -#include "code\game\objects\structures\noticeboard.dm" #include "code\game\objects\structures\plasticflaps.dm" #include "code\game\objects\structures\railing.dm" #include "code\game\objects\structures\safe.dm" @@ -1551,6 +1551,7 @@ #include "code\modules\admin\map_capture.dm" #include "code\modules\admin\NewBan.dm" #include "code\modules\admin\news.dm" +#include "code\modules\admin\persistence.dm" #include "code\modules\admin\player_notes.dm" #include "code\modules\admin\player_panel.dm" #include "code\modules\admin\topic.dm" @@ -3027,6 +3028,7 @@ #include "code\modules\paperwork\handlabeler.dm" #include "code\modules\paperwork\paper.dm" #include "code\modules\paperwork\paper_bundle.dm" +#include "code\modules\paperwork\paper_sticky.dm" #include "code\modules\paperwork\paperbin.dm" #include "code\modules\paperwork\paperplane.dm" #include "code\modules\paperwork\papershredder.dm" @@ -3036,6 +3038,15 @@ #include "code\modules\paperwork\silicon_photography.dm" #include "code\modules\paperwork\stamps.dm" #include "code\modules\persistence\persistence.dm" +#include "code\modules\persistence\filth.dm" +#include "code\modules\persistence\graffiti.dm" +#include "code\modules\persistence\noticeboard.dm" +#include "code\modules\persistence\datum\datum_filth.dm" +#include "code\modules\persistence\datum\datum_graffiti.dm" +#include "code\modules\persistence\datum\datum_paper.dm" +#include "code\modules\persistence\datum\datum_paper_sticky.dm" +#include "code\modules\persistence\datum\datum_trash.dm" +#include "code\modules\persistence\datum\persistence_datum.dm" #include "code\modules\planet\planet.dm" #include "code\modules\planet\time.dm" #include "code\modules\planet\virgo3b_vr.dm"
[thing]") + if(istype(thing, /obj/item/weapon/paper)) + LAZYADD(dat, "ReadWrite") + else if(istype(thing, /obj/item/weapon/photo)) + LAZYADD(dat, "Look") + LAZYADD(dat, "Remove