From 5b0eedcee55b66e39f8828c32768f43135410196 Mon Sep 17 00:00:00 2001 From: Crazylemon64 Date: Wed, 1 Aug 2018 00:58:13 -0700 Subject: [PATCH] Refactors buildmode into submodules, and adds an "atmos" buildmode Also makes the mode selection interface far less tedious/painful Adds unsimulated turf overriding, and admin logging Temporary commit for storing the sdql buildmode Adds extra documentation also requires a path to be selected for "advanced" and "fill" modes, now Spaces out mode switch buttons nicer removes need for debug rights to do a fill with mob paths Adds a bunch of extra fancy paths Fox Changes pt 1 Backports /tg/station changes Fixes a double-tap issue with dclick handling of certain mobs Also no longer shows the "selection canceled" if you haven't selected a region for the buildmode that supports region selection Updates buildmode stuff --- code/__DEFINES/misc.dm | 6 +- code/__HELPERS/unsorted.dm | 103 ++- code/_onclick/ai.dm | 3 +- code/_onclick/observer.dm | 3 +- code/datums/click_intercept.dm | 31 + code/modules/admin/buildmode.dm | 656 ------------------ code/modules/admin/verbs/modifyvariables.dm | 38 +- code/modules/buildmode/README.md | 297 ++++++++ code/modules/buildmode/bm_mode.dm | 87 +++ code/modules/buildmode/buildmode.dm | 127 ++++ code/modules/buildmode/buttons.dm | 88 +++ code/modules/buildmode/effects/line.dm | 28 + code/modules/buildmode/submodes/advanced.dm | 59 ++ code/modules/buildmode/submodes/area_edit.dm | 59 ++ code/modules/buildmode/submodes/atmos.dm | 72 ++ code/modules/buildmode/submodes/basic.dm | 50 ++ code/modules/buildmode/submodes/boom.dm | 32 + code/modules/buildmode/submodes/copy.dm | 27 + code/modules/buildmode/submodes/fill.dm | 49 ++ code/modules/buildmode/submodes/link.dm | 94 +++ code/modules/buildmode/submodes/mapgen.dm | 40 ++ code/modules/buildmode/submodes/save.dm | 28 + code/modules/buildmode/submodes/sdql.dm | 12 + code/modules/buildmode/submodes/throwing.dm | 25 + .../buildmode/submodes/variable_edit.dm | 55 ++ icons/misc/buildmode.dmi | Bin 1640 -> 1711 bytes icons/turf/overlays.dmi | Bin 5340 -> 5795 bytes paradise.dme | 18 +- 28 files changed, 1394 insertions(+), 693 deletions(-) create mode 100644 code/datums/click_intercept.dm delete mode 100644 code/modules/admin/buildmode.dm create mode 100644 code/modules/buildmode/README.md create mode 100644 code/modules/buildmode/bm_mode.dm create mode 100644 code/modules/buildmode/buildmode.dm create mode 100644 code/modules/buildmode/buttons.dm create mode 100644 code/modules/buildmode/effects/line.dm create mode 100644 code/modules/buildmode/submodes/advanced.dm create mode 100644 code/modules/buildmode/submodes/area_edit.dm create mode 100644 code/modules/buildmode/submodes/atmos.dm create mode 100644 code/modules/buildmode/submodes/basic.dm create mode 100644 code/modules/buildmode/submodes/boom.dm create mode 100644 code/modules/buildmode/submodes/copy.dm create mode 100644 code/modules/buildmode/submodes/fill.dm create mode 100644 code/modules/buildmode/submodes/link.dm create mode 100644 code/modules/buildmode/submodes/mapgen.dm create mode 100644 code/modules/buildmode/submodes/save.dm create mode 100644 code/modules/buildmode/submodes/sdql.dm create mode 100644 code/modules/buildmode/submodes/throwing.dm create mode 100644 code/modules/buildmode/submodes/variable_edit.dm diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 4612c8cdce3..a4bcbcadc4b 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -373,4 +373,8 @@ #define RETURN_TO_LOBBY 2 //Melting Temperatures for various specific objects -#define GIRDER_MELTING_TEMP 5000 \ No newline at end of file +#define GIRDER_MELTING_TEMP 5000 + +// Area selection defines +#define AREASELECT_CORNERA "corner A" +#define AREASELECT_CORNERB "corner B" diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 1080a4f628d..e01a1fd9895 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1785,28 +1785,99 @@ var/mob/dview/dview_mob = new for(var/type in types) var/typename = "[type]" var/static/list/TYPES_SHORTCUTS = list( + //longest paths comes first - otherwise they get shadowed by the more generic ones /obj/effect/decal/cleanable = "CLEANABLE", - /obj/item/radio/headset = "HEADSET", - /obj/item/clothing/head/helmet/space = "SPESSHELMET", - /obj/item/book/manual = "MANUAL", - /obj/item/reagent_containers/food/drinks = "DRINK", //longest paths comes first - /obj/item/reagent_containers/food = "FOOD", - /obj/item/reagent_containers = "REAGENT_CONTAINERS", - /obj/item = "WEAPON", - /obj/machinery/atmospherics = "ATMOS_MECH", - /obj/machinery/portable_atmospherics = "PORT_ATMOS", - /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack = "MECHA_MISSILE_RACK", - /obj/item/mecha_parts/mecha_equipment = "MECHA_EQUIP", - /obj/item/organ = "ORGAN", - /obj/item = "ITEM", - /obj/machinery = "MACHINERY", /obj/effect = "EFFECT", + /obj/item/ammo_casing = "AMMO", + /obj/item/book/manual = "MANUAL", + /obj/item/borg/upgrade = "BORG_UPGRADE", + /obj/item/cartridge = "PDA_CART", + /obj/item/clothing/head/helmet/space = "SPESSHELMET", + /obj/item/clothing/head = "HEAD", + /obj/item/clothing/under = "UNIFORM", + /obj/item/clothing/shoes = "SHOES", + /obj/item/clothing/suit = "SUIT", + /obj/item/clothing/gloves = "GLOVES", + /obj/item/clothing/mask/cigarette = "CIGARRETE", // oof + /obj/item/clothing/mask = "MASK", + /obj/item/clothing/glasses = "GLASSES", + /obj/item/clothing = "CLOTHING", + /obj/item/grenade/clusterbuster = "CLUSTERBUSTER", + /obj/item/grenade = "GRENADE", + /obj/item/gun = "GUN", + /obj/item/implant = "IMPLANT", + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack = "MECHA_MISSILE_RACK", + /obj/item/mecha_parts/mecha_equipment/weapon = "MECHA_WEAPON", + /obj/item/mecha_parts/mecha_equipment = "MECHA_EQUIP", + /obj/item/melee = "MELEE", + /obj/item/mmi = "MMI", + /obj/item/nullrod = "NULLROD", + /obj/item/organ/external = "EXT_ORG", + /obj/item/organ/internal/cyberimp = "CYBERIMP", + /obj/item/organ/internal = "INT_ORG", + /obj/item/organ = "ORGAN", + /obj/item/pda = "PDA", + /obj/item/projectile = "PROJ", + /obj/item/radio/headset = "HEADSET", + /obj/item/reagent_containers/glass/beaker = "BEAKER", + /obj/item/reagent_containers/glass/bottle = "BOTTLE", + /obj/item/reagent_containers/food/pill/patch = "PATCH", + /obj/item/reagent_containers/food/pill = "PILL", + /obj/item/reagent_containers/food/drinks = "DRINK", + /obj/item/reagent_containers/food = "FOOD", + /obj/item/reagent_containers/syringe = "SYRINGE", + /obj/item/reagent_containers = "REAGENT_CONTAINERS", + /obj/item/robot_parts = "ROBOT_PARTS", + /obj/item/seeds = "SEED", + /obj/item/slime_extract = "SLIME_CORE", + /obj/item/spacepod_equipment/weaponry = "POD_WEAPON", + /obj/item/spacepod_equipment = "POD_EQUIP", + /obj/item/stack/sheet/mineral = "MINERAL", + /obj/item/stack/sheet = "SHEET", + /obj/item/stack/tile = "TILE", + /obj/item/stack = "STACK", + /obj/item/stock_parts/cell = "POWERCELL", + /obj/item/stock_parts = "STOCK_PARTS", + /obj/item/storage/firstaid = "FIRSTAID", + /obj/item/storage = "STORAGE", + /obj/item/tank = "GAS_TANK", + /obj/item/toy/crayon = "CRAYON", + /obj/item/toy = "TOY", + /obj/item = "ITEM", + /obj/machinery/atmospherics = "ATMOS_MACH", + /obj/machinery/computer = "CONSOLE", + /obj/machinery/door/airlock = "AIRLOCK", + /obj/machinery/door = "DOOR", + /obj/machinery/kitchen_machine = "KITCHEN", + /obj/machinery/portable_atmospherics/canister = "CANISTER", + /obj/machinery/portable_atmospherics = "PORT_ATMOS", + /obj/machinery/power = "POWER", + /obj/machinery/telecomms = "TCOMMS", + /obj/machinery = "MACHINERY", + /obj/mecha = "MECHA", + /obj/structure/closet/crate = "CRATE", + /obj/structure/closet = "CLOSET", + /obj/structure/statue = "STATUE", + /obj/structure/chair = "CHAIR", // oh no + /obj/structure/stool/bed = "BED", + /obj/structure/stool = "STOOL", + /obj/structure/table = "TABLE", + /obj/structure = "STRUCTURE", + /obj/vehicle = "VEHICLE", /obj = "O", /datum = "D", - /turf/simulated/floor = "FLOOR", - /turf/simulated/wall = "WALL", + /turf/simulated/floor = "SIM_FLOOR", + /turf/simulated/wall = "SIM_WALL", + /turf/unsimulated/floor = "UNSIM_FLOOR", + /turf/unsimulated/wall = "UNSIM_WALL", /turf = "T", + /mob/living/carbon/alien = "XENO", + /mob/living/carbon/human = "HUMAN", /mob/living/carbon = "CARBON", + /mob/living/silicon/robot = "CYBORG", + /mob/living/silicon/ai = "AI", + /mob/living/silicon = "SILICON", + /mob/living/simple_animal/bot = "BOT", /mob/living/simple_animal = "SIMPLE", /mob/living = "LIVING", /mob = "M" diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index b2521527a98..0b51e52d3ea 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -11,7 +11,8 @@ */ /mob/living/silicon/ai/DblClickOn(var/atom/A, params) if(client.click_intercept) - client.click_intercept.InterceptClickOn(src, params, A) + // Not doing a click intercept here, because otherwise we double-tap with the `ClickOn` proc. + // But we return here since we don't want to do regular dblclick handling return if(control_disabled || stat) return diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index d04abfe5a87..797384b8336 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -1,6 +1,7 @@ /mob/dead/observer/DblClickOn(var/atom/A, var/params) if(client.click_intercept) - client.click_intercept.InterceptClickOn(src, params, A) + // Not doing a click intercept here, because otherwise we double-tap with the `ClickOn` proc. + // But we return here since we don't want to do regular dblclick handling return if(can_reenter_corpse && mind && mind.current) diff --git a/code/datums/click_intercept.dm b/code/datums/click_intercept.dm new file mode 100644 index 00000000000..d3e9bccbda0 --- /dev/null +++ b/code/datums/click_intercept.dm @@ -0,0 +1,31 @@ +/datum/click_intercept + var/client/holder = null + var/list/obj/screen/buttons = list() + +/datum/click_intercept/New(client/C) + create_buttons() + enter(C) + return ..() + +/datum/click_intercept/Destroy() + holder.screen -= buttons + holder.click_intercept = null + holder.show_popup_menus = TRUE + holder = null + QDEL_LIST(buttons) + return ..() + +/datum/click_intercept/proc/enter(client/C) + holder = C + holder.click_intercept = src + holder.show_popup_menus = FALSE + holder.screen += buttons + +/datum/click_intercept/proc/quit() + qdel(src) + +/datum/click_intercept/proc/create_buttons() + return + +/datum/click_intercept/proc/InterceptClickOn(user,params,atom/object) + return \ No newline at end of file diff --git a/code/modules/admin/buildmode.dm b/code/modules/admin/buildmode.dm deleted file mode 100644 index b04b9d2b96a..00000000000 --- a/code/modules/admin/buildmode.dm +++ /dev/null @@ -1,656 +0,0 @@ -#define BASIC_BUILDMODE 1 -#define ADV_BUILDMODE 2 -#define VAR_BUILDMODE 3 -#define THROW_BUILDMODE 4 -#define AREA_BUILDMODE 5 -#define COPY_BUILDMODE 6 -#define AREAEDIT_BUILDMODE 7 -#define FILL_BUILDMODE 8 -#define LINK_BUILDMODE 9 -#define BOOM_BUILDMODE 10 -#define SAVE_BUILDMODE 11 -#define NUM_BUILDMODES 11 - -/obj/screen/buildmode - icon = 'icons/misc/buildmode.dmi' - var/datum/click_intercept/buildmode/bd - layer = HUD_LAYER_BUILDMODE - -/obj/screen/buildmode/New(bld) - ..() - bd = bld - -/obj/screen/buildmode/mode - name = "Toggle Mode" - icon_state = "buildmode1" - screen_loc = "NORTH,WEST" - -/obj/screen/buildmode/mode/Click(location, control, params) - var/list/pa = params2list(params) - - if(pa.Find("left")) - bd.toggle_modes() - else if(pa.Find("right")) - bd.change_settings(usr) - update_icon() - return 1 - -/obj/screen/buildmode/mode/update_icon() - icon_state = "buildmode[bd.mode]" - return - -/obj/screen/buildmode/help - icon_state = "buildhelp" - screen_loc = "NORTH,WEST+1" - name = "Buildmode Help" - -/obj/screen/buildmode/help/Click() - bd.show_help(usr) - return 1 - -/obj/screen/buildmode/bdir - icon_state = "build" - screen_loc = "NORTH,WEST+2" - name = "Change Dir" - -/obj/screen/buildmode/bdir/update_icon() - dir = bd.build_dir - return - -/obj/screen/buildmode/bdir/Click() - bd.change_dir() - update_icon() - return 1 - -/obj/screen/buildmode/quit - icon_state = "buildquit" - screen_loc = "NORTH,WEST+3" - name = "Quit Buildmode" - -/obj/screen/buildmode/quit/Click() - bd.quit() - return 1 - -/obj/screen/buildmode/dir/Click() - bd.change_dir() - update_icon() - return 1 - -/obj/effect/buildmode_reticule - var/image/I - var/client/cl - anchored = TRUE - -/obj/effect/buildmode_reticule/New(var/turf/t, var/client/c) - loc = t - I = image('icons/mob/blob.dmi', t, "marker",19.0,2) // Sprite reuse wooo - if(c) - cl = c - cl.images += I - else - log_runtime(EXCEPTION("Buildmode reticule created without a client!"), src) - -/obj/effect/buildmode_reticule/proc/deselect() - qdel(src) - -/obj/effect/buildmode_reticule/Destroy() - cl.images -= I - cl = null - qdel(I) - return ..() - -/obj/effect/buildmode_line - var/image/I - var/client/cl - -/obj/effect/buildmode_line/New(var/client/c, var/atom/atom_a, var/atom/atom_b, var/linename) - name = linename - loc = get_turf(atom_a) - I = image('icons/misc/mark.dmi', src, "line", 19.0) - var/x_offset = ((atom_b.x * 32) + atom_b.pixel_x) - ((atom_a.x * 32) + atom_a.pixel_x) - var/y_offset = ((atom_b.y * 32) + atom_b.pixel_y) - ((atom_a.y * 32) + atom_a.pixel_y) - - var/matrix/M = matrix() - M.Translate(0, 16) - M.Scale(1, sqrt((x_offset * x_offset) + (y_offset * y_offset)) / 32) - M.Turn(90 - Atan2(x_offset, y_offset)) // So... You pass coords in order x,y to this version of atan2. It should be called acsc2. - M.Translate(atom_a.pixel_x, atom_a.pixel_y) - - transform = M - cl = c - cl.images += I - -/obj/effect/buildmode_line/Destroy() - if(I) - if(istype(cl)) - cl.images -= I - cl = null - QDEL_NULL(I) - return ..() - -/datum/click_intercept - var/client/holder = null - var/list/obj/screen/buttons = list() - -/datum/click_intercept/New(client/c) - create_buttons() - holder = c - holder.click_intercept = src - holder.show_popup_menus = 0 - holder.screen += buttons - -/datum/click_intercept/Destroy() - QDEL_LIST(buttons) - return ..() - - -/datum/click_intercept/proc/create_buttons() - return - -/datum/click_intercept/proc/InterceptClickOn(user,params,atom/object) - return - -/datum/click_intercept/proc/quit() - holder.screen -= buttons - holder.click_intercept = null - holder.show_popup_menus = 1 - qdel(src) - - - -/datum/click_intercept/buildmode - var/mode = BASIC_BUILDMODE - var/build_dir = SOUTH - var/atom/movable/throw_atom = null - var/obj/effect/buildmode_reticule/cornerA = null - var/obj/effect/buildmode_reticule/cornerB = null - var/generator_path = null - var/varholder = "name" - var/valueholder = "derp" - var/objholder = /obj/structure/closet - var/area/storedarea = null - var/image/areaimage - var/atom/movable/stored = null - var/list/link_lines = list() - var/obj/link_obj - var/valid_links = 0 - //Explosion mode - var/devastation = -1 - var/heavy = -1 - var/light = -1 - var/flash = -1 - var/flames = -1 - // Saving mode - var/use_json = TRUE - -/datum/click_intercept/buildmode/New(client/c) - ..() - areaimage = image('icons/turf/areas.dmi',null,"yellow") - holder.images += areaimage - -/datum/click_intercept/buildmode/Destroy() - stored = null - Reset() - areaimage.loc = null - qdel(areaimage) - return ..() - -/datum/click_intercept/buildmode/create_buttons() - buttons += new /obj/screen/buildmode/mode(src) - buttons += new /obj/screen/buildmode/help(src) - buttons += new /obj/screen/buildmode/bdir(src) - buttons += new /obj/screen/buildmode/quit(src) - -/datum/click_intercept/buildmode/proc/toggle_modes() - mode = (mode % NUM_BUILDMODES) +1 - Reset() - return - -/datum/click_intercept/buildmode/proc/show_help(mob/user) - switch(mode) - if(BASIC_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button = Construct / Upgrade") - to_chat(user, "Right Mouse Button = Deconstruct / Delete / Downgrade") - to_chat(user, "Left Mouse Button + ctrl = R-Window") - to_chat(user, "Left Mouse Button + alt = Airlock") - to_chat(user, "") - to_chat(user, "Use the button in the upper left corner to") - to_chat(user, "change the direction of built objects.") - to_chat(user, "***********************************************************") - if(ADV_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Right Mouse Button on buildmode button = Set object type") - to_chat(user, "Left Mouse Button on turf/obj = Place objects") - to_chat(user, "Right Mouse Button = Delete objects") - to_chat(user, "") - to_chat(user, "Use the button in the upper left corner to") - to_chat(user, "change the direction of built objects.") - to_chat(user, "***********************************************************") - if(VAR_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Right Mouse Button on buildmode button = Select var(type) & value") - to_chat(user, "Left Mouse Button on turf/obj/mob = Set var(type) & value") - to_chat(user, "Right Mouse Button on turf/obj/mob = Reset var's value") - to_chat(user, "***********************************************************") - if(THROW_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on turf/obj/mob = Select") - to_chat(user, "Right Mouse Button on turf/obj/mob = Throw") - to_chat(user, "***********************************************************") - if(AREA_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner") - to_chat(user, "Right Mouse Button on buildmode button = Select generator") - to_chat(user, "***********************************************************") - if(COPY_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on obj/turf/mob = Spawn a Copy of selected target") - to_chat(user, "Right Mouse Button on obj/mob = Select target to copy") - to_chat(user, "***********************************************************") - if(AREAEDIT_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on obj/turf/mob = Paint area") - to_chat(user, "Right Mouse Button on obj/turf/mob = Select area to paint") - to_chat(user, "Right Mouse Button on buildmode button = Create new area") - to_chat(user, "***********************************************************") - if(FILL_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner") - to_chat(user, "Left Mouse Button + Alt on turf/obj/mob = Delete region") - to_chat(user, "Right Mouse Button on buildmode button = Select object type") - to_chat(user, "***********************************************************") - if(LINK_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on obj = Select button to link") - to_chat(user, "Right Mouse Button on obj = Link/unlink to selected button") - to_chat(user, "***********************************************************") - if(BOOM_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Mouse Button on obj = Kaboom") - to_chat(user, "***********************************************************") - if(SAVE_BUILDMODE) - to_chat(user, "***********************************************************") - to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner") - to_chat(user, "***********************************************************") - - - - -/datum/click_intercept/buildmode/proc/change_settings(mob/user) - switch(mode) - if(BASIC_BUILDMODE) - - return 1 - if(ADV_BUILDMODE) - var/target_path = input(user,"Enter typepath:" ,"Typepath","/obj/structure/closet") - objholder = text2path(target_path) - if(!ispath(objholder)) - objholder = pick_closest_path(target_path) - if(!objholder || ispath(objholder, /area)) - objholder = /obj/structure/closet - alert("That path is not allowed.") - else - if(ispath(objholder,/mob) && !check_rights(R_DEBUG,0)) - objholder = /obj/structure/closet - if(VAR_BUILDMODE) - var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine") - - varholder = input(user,"Enter variable name:" ,"Name", "name") - if(varholder in locked && !check_rights(R_DEBUG,0)) - - return 1 - var/thetype = input(user,"Select variable type:" ,"Type") in list("text","number","mob-reference","obj-reference","turf-reference") - if(!thetype) return 1 - switch(thetype) - if("text") - valueholder = input(user,"Enter variable value:" ,"Value", "value") as text - if("number") - valueholder = input(user,"Enter variable value:" ,"Value", 123) as num - if("mob-reference") - valueholder = input(user,"Enter variable value:" ,"Value") as mob in mob_list - if("obj-reference") - valueholder = input(user,"Enter variable value:" ,"Value") as obj in world - if("turf-reference") - valueholder = input(user,"Enter variable value:" ,"Value") as turf in world - if(AREA_BUILDMODE) - var/list/gen_paths = subtypesof(/datum/mapGenerator) - - var/type = input(user,"Select Generator Type","Type") as null|anything in gen_paths - if(!type) return - - generator_path = type - cornerA = null - cornerB = null - if(AREAEDIT_BUILDMODE) - var/target_path = input(user,"Enter typepath:", "Typepath", "/area") - var/areatype = text2path(target_path) - if(ispath(areatype,/area)) - var/areaname = input(user,"Enter area name:", "Area name", "Area") - if(!areaname || !length(areaname)) - return - storedarea = new areatype - storedarea.power_equip = 0 - storedarea.power_light = 0 - storedarea.power_environ = 0 - storedarea.always_unpowered = 0 - storedarea.name = areaname - areaimage.loc = storedarea - if(FILL_BUILDMODE) - var/target_path = input(user,"Enter typepath:" ,"Typepath","/obj/structure/closet") - objholder = text2path(target_path) - if(!ispath(objholder)) - objholder = pick_closest_path(target_path) - if(!objholder || ispath(objholder, /area)) - objholder = /obj/structure/closet - return - else - if(ispath(objholder,/mob) && !check_rights(R_DEBUG,0)) - objholder = /obj/structure/closet - deselect_region() - if(BOOM_BUILDMODE) - devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null - if(devastation == null) devastation = -1 - heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null - if(heavy == null) heavy = -1 - light = input("Range of light impact. -1 to none", text("Input")) as num|null - if(light == null) light = -1 - flash = input("Range of flash. -1 to none", text("Input")) as num|null - if(flash == null) flash = -1 - flames = input("Range of flames. -1 to none", text("Input")) as num|null - if(flames == null) flames = -1 - - if(SAVE_BUILDMODE) - use_json = (alert("Would you like to use json (Default is \"Yes\")?",,"Yes","No") == "Yes") - -/datum/click_intercept/buildmode/proc/change_dir() - switch(build_dir) - if(NORTH) - build_dir = EAST - if(EAST) - build_dir = SOUTH - if(SOUTH) - build_dir = WEST - if(WEST) - build_dir = NORTHWEST - if(NORTHWEST) - build_dir = NORTH - return 1 - -/datum/click_intercept/buildmode/proc/deselect_region() - QDEL_NULL(cornerA) - QDEL_NULL(cornerB) - -/datum/click_intercept/buildmode/proc/Reset()//Reset temporary variables - deselect_region() - if(mode == AREAEDIT_BUILDMODE) - areaimage.loc = storedarea - else - areaimage.loc = null - for(var/obj/effect/buildmode_line/L in link_lines) - qdel(L) - link_lines -= L - -/datum/click_intercept/buildmode/proc/select_tile(var/turf/T) - return new /obj/effect/buildmode_reticule(T, holder) - -/proc/togglebuildmode(mob/M as mob in player_list) - set name = "Toggle Build Mode" - set category = "Event" - - if(M.client) - if(istype(M.client.click_intercept,/datum/click_intercept/buildmode)) - var/datum/click_intercept/buildmode/B = M.client.click_intercept - B.quit() - log_admin("[key_name(usr)] has left build mode.") - else - new/datum/click_intercept/buildmode(M.client) - message_admins("[key_name_admin(usr)] has entered build mode.") - log_admin("[key_name(usr)] has entered build mode.") - -/datum/click_intercept/buildmode/InterceptClickOn(user,params,atom/object) //Click Intercept - var/list/pa = params2list(params) - var/right_click = pa.Find("right") - var/left_click = pa.Find("left") - var/alt_click = pa.Find("alt") - var/ctrl_click = pa.Find("ctrl") - - . = 1 - switch(mode) - if(BASIC_BUILDMODE) - if(istype(object,/turf) && left_click && !alt_click && !ctrl_click) - var/turf/T = object - if(istype(object,/turf/space)) - T.ChangeTurf(/turf/simulated/floor/plasteel) - else if(istype(object,/turf/simulated/floor)) - T.ChangeTurf(/turf/simulated/wall) - else if(istype(object,/turf/simulated/wall)) - T.ChangeTurf(/turf/simulated/wall/r_wall) - log_admin("Build Mode: [key_name(user)] built [T] at ([T.x],[T.y],[T.z])") - return - else if(right_click) - log_admin("Build Mode: [key_name(user)] deleted [object] at ([object.x],[object.y],[object.z])") - if(istype(object,/turf/simulated/wall)) - var/turf/T = object - T.ChangeTurf(/turf/simulated/floor/plasteel) - else if(istype(object,/turf/simulated/floor)) - var/turf/T = object - T.ChangeTurf(/turf/space) - else if(istype(object,/turf/simulated/wall/r_wall)) - var/turf/T = object - T.ChangeTurf(/turf/simulated/wall) - else if(istype(object,/obj)) - qdel(object) - return - else if(istype(object,/turf) && alt_click && left_click) - log_admin("Build Mode: [key_name(user)] built an airlock at ([object.x],[object.y],[object.z])") - new/obj/machinery/door/airlock(get_turf(object)) - else if(istype(object,/turf) && ctrl_click && left_click) - var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object)) - WIN.setDir(build_dir) - log_admin("Build Mode: [key_name(user)] built a window at ([object.x],[object.y],[object.z])") - - if(ADV_BUILDMODE) - if(left_click) - if(ispath(objholder,/turf)) - var/turf/T = get_turf(object) - log_admin("Build Mode: [key_name(user)] modified [T] ([T.x],[T.y],[T.z]) to [objholder]") - T.ChangeTurf(objholder) - else if(!isnull(objholder)) - var/obj/A = new objholder (get_turf(object)) - A.setDir(build_dir) - log_admin("Build Mode: [key_name(user)] modified [A]'s ([A.x],[A.y],[A.z]) dir to [build_dir]") - else - to_chat(user, "Select object type first.") - else if(right_click) - if(isobj(object)) - log_admin("Build Mode: [key_name(user)] deleted [object] at ([object.x],[object.y],[object.z])") - qdel(object) - - if(VAR_BUILDMODE) - if(left_click) //I cant believe this shit actually compiles. - if(object.vars.Find(varholder)) - log_admin("Build Mode: [key_name(user)] modified [object.name]'s [varholder] to [valueholder]") - object.vars[varholder] = valueholder - else - to_chat(user, "[initial(object.name)] does not have a var called '[varholder]'") - if(right_click) - if(object.vars.Find(varholder)) - log_admin("Build Mode: [key_name(user)] modified [object.name]'s [varholder] to [valueholder]") - object.vars[varholder] = initial(object.vars[varholder]) - else - to_chat(user, "[initial(object.name)] does not have a var called '[varholder]'") - - if(THROW_BUILDMODE) - if(left_click) - if(isturf(object)) - return - throw_atom = object - if(right_click) - if(throw_atom) - throw_atom.throw_at(object, 10, 1,user) - log_admin("Build Mode: [key_name(user)] threw [throw_atom] at [object] ([object.x],[object.y],[object.z])") - if(AREA_BUILDMODE) - if(!cornerA) - cornerA = select_tile(get_turf(object)) - return - if(cornerA && !cornerB) - cornerB = select_tile(get_turf(object)) - if(left_click) //rectangular - if(cornerA && cornerB) - if(!generator_path) - to_chat(user, "Select generator type first.") - else - var/datum/mapGenerator/G = new generator_path - G.defineRegion(cornerA.loc,cornerB.loc,1) - G.generate() - deselect_region() - return - - //Something wrong - Reset - deselect_region() - if(COPY_BUILDMODE) - if(left_click) - var/turf/T = get_turf(object) - if(stored) - DuplicateObject(stored, perfectcopy=1, sameloc=0,newloc=T) - else if(right_click) - if(ismovableatom(object)) // No copying turfs for now. - to_chat(user, "[object] set as template.") - stored = object - if(AREAEDIT_BUILDMODE) - if(left_click) - if(!storedarea) - return - var/turf/T = get_turf(object) - if(get_area(T) != storedarea) - storedarea.contents.Add(T) - else if(right_click) - var/turf/T = get_turf(object) - storedarea = get_area(T) - areaimage.loc = storedarea - if(FILL_BUILDMODE) - if(!cornerA) - cornerA = select_tile(get_turf(object)) - return - if(cornerA && !cornerB) - cornerB = select_tile(get_turf(object)) - if(left_click) //rectangular - if(cornerA && cornerB) - if(alt_click) - empty_region(block(get_turf(cornerA),get_turf(cornerB))) - deselect_region() - else - if(!objholder) - to_chat(user, "Select object type first.") - else - for(var/turf/T in block(get_turf(cornerA),get_turf(cornerB))) - if(ispath(objholder,/turf)) - T.ChangeTurf(objholder) - else - var/obj/A = new objholder(T) - A.setDir(build_dir) - deselect_region() - return - - //Something wrong - Reset - deselect_region() - if(LINK_BUILDMODE) - if(left_click && istype(object, /obj/machinery)) - link_obj = object - if(right_click && istype(object, /obj/machinery)) - if(istype(link_obj, /obj/machinery/door_control) && istype(object, /obj/machinery/door/airlock)) - var/obj/machinery/door_control/M = link_obj - var/obj/machinery/door/airlock/P = object - if(!M.id || M.id == "") - M.id = input(holder, "Please select an ID for the button", "Buildmode", "") - if(!M.id || M.id == "") - goto(line_jump) - if(P.id_tag == M.id && (P in range(M.range, M)) && P.id_tag && P.id_tag != "") - P.id_tag = null - to_chat(holder, "[P] unlinked.") - goto(line_jump) - if(!M.normaldoorcontrol) - if(link_lines.len && alert(holder, "Warning: This will disable links to connected pod doors. Continue?", "Buildmode", "Yes", "No") == "No") - goto(line_jump) - M.normaldoorcontrol = 1 - if(P.id_tag && alert(holder, "Warning: This will unlink something else from the door. Continue?", "Buildmode", "Yes", "No") == "No") - goto(line_jump) - P.id_tag = M.id - if(istype(link_obj, /obj/machinery/door_control) && istype(object, /obj/machinery/door/poddoor)) - var/obj/machinery/door_control/M = link_obj - var/obj/machinery/door/poddoor/P = object - if(!M.id || M.id == "") - M.id = input(holder, "Please select an ID for the button", "Buildmode", "") - if(!M.id || M.id == "") - goto(line_jump) - if(P.id_tag == M.id && P.id_tag && P.id_tag != "") - P.id_tag = null - to_chat(holder, "[P] unlinked.") - goto(line_jump) - if(M.normaldoorcontrol) - if(link_lines.len && alert(holder, "Warning: This will disable links to connected airlocks. Continue?", "Buildmode", "Yes", "No") == "No") - goto(line_jump) - M.normaldoorcontrol = 0 - if(!M.id || M.id == "") - M.id = input(holder, "Please select an ID for the button", "Buildmode", "") - if(!M.id || M.id == "") - goto(line_jump) - if(P.id_tag && P.id_tag != 1 && alert(holder, "Warning: This will unlink something else from the door. Continue?", "Buildmode", "Yes", "No") == "No") - goto(line_jump) - P.id_tag = M.id - - line_jump // For the goto - valid_links = 0 - for(var/obj/effect/buildmode_line/L in link_lines) - qdel(L) - link_lines -= L - - if(istype(link_obj, /obj/machinery/door_control)) - var/obj/machinery/door_control/M = link_obj - for(var/obj/machinery/door/airlock/P in range(M.range,M)) - if(P.id_tag == M.id) - var/obj/effect/buildmode_line/L = new(holder, M, P, "[M.name] to [P.name]") - L.color = M.normaldoorcontrol ? "#339933" : "#993333" - if(M.normaldoorcontrol) - valid_links = 1 - link_lines += L - var/obj/effect/buildmode_line/L2 = new(holder, P, M, "[M.name] to [P.name]") // Yes, reversed one so that you can see it from both sides. - L2.color = L.color - link_lines += L2 - for(var/obj/machinery/door/poddoor/P in airlocks) - if(P.id_tag == M.id) - var/obj/effect/buildmode_line/L = new(holder, M, P, "[M.name] to [P.name]") - L.color = M.normaldoorcontrol ? "#993333" : "#339933" - if(M.normaldoorcontrol) - valid_links = 0 - link_lines += L - var/obj/effect/buildmode_line/L2 = new(holder, P, M, "[M.name] to [P.name]") // Yes, reversed one so that you can see it from both sides. - L2.color = L.color - link_lines += L2 - if(BOOM_BUILDMODE) - explosion(object, devastation, heavy, light, flash, null, TRUE, flames) - if(SAVE_BUILDMODE) - if(!cornerA) - cornerA = select_tile(get_turf(object)) - return - if(!cornerB) - cornerB = select_tile(get_turf(object)) - if(left_click) - if(cornerA && cornerB) - var/turf/A = get_turf(cornerA) - var/turf/B = get_turf(cornerB) - deselect_region() // So we don't read our own reticules - var/map_name = input(holder, "Please select a name for your map", "Buildmode", "") - if(map_name == "") - return - var/map_flags = 0 - if(use_json) - map_flags = 32 // Magic number defined in `writer.dm` that I can't use directly - // because #defines are for some reason our coding standard - var/our_map = maploader.save_map(A,B,map_name,map_flags) - holder << ftp(our_map) // send the map they've made! Or are stealing, whatever - to_chat(holder, "Map saving complete! [our_map]") - return - - deselect_region() diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index c1e28a48f8d..d899fda5b56 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -516,6 +516,24 @@ var/list/VVpixelmovement = list("step_x", "step_y", "step_size", "bound_height", log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]") message_admins("[key_name_admin(src)] modified [original_name]'s varlist [objectvar]: [original_var]=[new_var]") +/proc/vv_varname_lockcheck(param_var_name) + if(param_var_name in VVlocked) + if(!check_rights(R_DEBUG)) + return FALSE + if(param_var_name in VVckey_edit) + if(!check_rights(R_EVENT | R_DEBUG)) + return FALSE + if(param_var_name in VVicon_edit_lock) + if(!check_rights(R_EVENT | R_DEBUG)) + return FALSE + if(param_var_name in VVpixelmovement) + if(!check_rights(R_DEBUG)) + return FALSE + var/prompt = alert(usr, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") + if(prompt != "Continue") + return FALSE + return TRUE + /client/proc/modify_variables(atom/O, param_var_name = null, autodetect_class = 0) if(!check_rights(R_VAREDIT)) return @@ -544,25 +562,11 @@ var/list/VVpixelmovement = list("step_x", "step_y", "step_size", "bound_height", if(!O.can_vv_get(variable)) return + if(!vv_varname_lockcheck(variable)) + return + var_value = O.vars[variable] - if(variable in VVlocked) - if(!check_rights(R_DEBUG)) - return - if(variable in VVckey_edit) - if(!check_rights(R_EVENT | R_DEBUG)) - return - if(variable in VVicon_edit_lock) - if(!check_rights(R_EVENT | R_DEBUG)) - return - if(variable in VVpixelmovement) - if(!check_rights(R_DEBUG)) - return - var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT") - if(prompt != "Continue") - return - - var/default = vv_get_class(var_value) if(isnull(default)) diff --git a/code/modules/buildmode/README.md b/code/modules/buildmode/README.md new file mode 100644 index 00000000000..37ce2ce6b1b --- /dev/null +++ b/code/modules/buildmode/README.md @@ -0,0 +1,297 @@ +# Buildmode + +## Code layout + +### Buildmode + +Manager for buildmode modes. Contains logic to manage switching between each mode, and presenting a suitable user interface. + +### Effects + +Special graphics used by buildmode modes for user interface purposes. + +### Buildmode Mode + +Implementer of buildmode behaviors. + +Existing varieties: + ++ Basic + + **Description**: + + Allows creation of simple structures consisting of floors, walls, windows, and airlocks. + + **Controls**: + + + *Left click a turf*: + + "Upgrades" the turf based on the following rules below: + + + Space -> Tiled floor + + Simulated floor -> Regular wall + + Wall -> Reinforced wall + + + *Right click a turf*: + + "Downgrades" the turf based on the following rules below: + + + Reinforced wall -> Regular wall + + Wall -> Tiled floor + + Simulated floor -> Space + + + *Right click an object*: + + Deletes the clicked object. + + + *Alt+Left click a location*: + + Places an airlock at the clicked location. + + + *Ctrl+Left click a location*: + + Places a window at the clicked location. + ++ Advanced + + **Description**: + + Creates an instance of a configurable atom path where you click. + + **Controls**: + + + *Right click on the mode selector*: + + Choose a path to spawn. + + + *Alt+Left click a turf, object, or mob*: + + Select the type of the object clicked. + + + *Left click a location* (requires chosen path): + + Place an instance of the chosen path at the location. + + + *Right click an object*: + + Delete the object. + ++ Fill + + **Description**: + + Creates an instance of an atom path on every tile in a chosen region. + + With a special control input, instead deletes everything within the region. + + **Controls**: + + + *Right click on the mode selector*: + + Choose a path to spawn. + + + *Left click on a region* (requires chosen path): + + Fill the region with the chosen path. + + + *Alt+Left click on a region*: + + Deletes everything within the region. + + + *Right click during region selection*: + + Cancel region selection. + ++ Atmos + + **Description**: + + Fills a region with configurable atmos. By default, ignores unsimulated turfs, but is able to "overwrite" the atmos of unsimulated turfs with a special control input. + + By default, fills a region with a breathable, standard atmosphere. + + **Controls**: + + + *Right click on the mode selector icon*: + + Set the following traits: + + + Total Pressure + + Temperature + + Partial Pressure Ratio (PPR) Oxygen + + PPR Nitrogen + + PPR Plasma + + PPR CO2 + + PPR N2O + + + *Left click a region*: + + Fill the region with the configured atmos. + + + *Control+Left click a region*: + + As with the regular left click, but also "overwrites" the base atmos of any unsimulated turfs in the region - such as space turfs. + + + *Right click during region selection*: + + Cancel region selection. + ++ Copy + + **Description**: + + Take an existing object in the world, and place duplicates with identical attributes where you click. + + May not always work nicely - "deep" variables such as lists or datums may malfunction. + + **Controls**: + + + *Right click an existing object*: + + Select the clicked object as a template. + + + *Left click a location* (Requires a selected object as template): + + Place a duplicate of the template at the clicked location. + ++ Link + + **Description**: + + Form links between door control buttons and either airlocks or pod bay doors. + + The selected button will be highlighted, and visible lines will be drawn between the doors it is linked to and itself. + + **Controls**: + + + *Left click a door control button*: + + Makes the button active, and show what doors it is linked to. + + + *Right click an airlock* (requires active button): + + Links the airlock to the active button. Will remove all links from the button first, if the button is linked to pod bay doors. + + + *Right click a pod bay door* (requires active button): + + Links the pod bay door to the active button. Will remove all links from the button first, if the button is linked to regular airlocks. + ++ Area Edit + + **Description**: + + Modifies and creates areas. + + The active area will be highlighted in yellow. + + **Controls**: + + + *Right click the mode selector*: + + Create a new area, and make it active. + + + *Right click an existing area*: + + Make the clicked area active. + + + *Left click a turf*: + + When an area is active, adds the turf to the active area. + ++ Var Edit + + **Description**: + + Allows for setting and resetting variables of objects with a click. + + If the object does not have the var, will do nothing and print a warning message. + + **Controls**: + + + *Right click the mode selector*: + + Choose which variable to set, and what to set it to. + + + *Left click an atom*: + + Change the clicked atom's variables as configured. + + + *Right click an atom*: + + Reset the targeted variable to its original value in the code. + ++ Map Generator + + **Description**: + + Fills rectangular regions with algorithmically generated content. Right click during region selection to cancel. + + See the `procedural_mapping` module for the generators themselves. + + **Controls**: + + + *Right-click on the mode selector*: + + Select a map generator from all the generators present in the codebase. + + + *Left click two corners of an area*: + + Use the generator to populate the region. + + + *Right click during region selection*: + + Cancel region selection. + ++ Save + + **Description**: + + Captures a rectangular region in a `.dmm` format, which can be loaded back later using the "Place Map Template" debug verb. + + Keep in mind this feature is somewhat experimental, and may not always work. + + **Controls**: + + + *Right click on the mode selector*: + + Configure whether to save in either JSON mode or not. + + + *Left click two corners of an area*: + + Save the region to a `.dmm` file. You will be prompted for where to save this - a copy will be saved in the `_maps/quicksave` folder. + + + *Right click during region selection*: + + Cancel region selection. + ++ Throwing + + **Description**: + + Select an object with left click, and right click to throw it towards where you clicked. + + **Controls**: + + + *Left click on a movable atom*: + + Select the atom for throwing. + + + *Right click on a location*: + + Throw the selected atom towards that location. + ++ Boom + + **Description**: + + Make explosions where you click. + + **Controls**: + + + *Right click the mode selector*: + + Configure the explosion size. + + + *Left click a location*: + + Cause an explosion where you clicked. \ No newline at end of file diff --git a/code/modules/buildmode/bm_mode.dm b/code/modules/buildmode/bm_mode.dm new file mode 100644 index 00000000000..de4c09a192d --- /dev/null +++ b/code/modules/buildmode/bm_mode.dm @@ -0,0 +1,87 @@ +/datum/buildmode_mode + var/key = "oops" + + var/datum/click_intercept/buildmode/BM + + var/use_corner_selection = FALSE + var/processing_selection = FALSE + var/list/preview + var/turf/cornerA + var/turf/cornerB + +/datum/buildmode_mode/New(datum/click_intercept/buildmode/newBM) + BM = newBM + preview = list() + return ..() + +/datum/buildmode_mode/Destroy() + Reset() + return ..() + +/datum/buildmode_mode/proc/enter_mode(datum/click_intercept/buildmode/BM) + return + +/datum/buildmode_mode/proc/exit_mode(datum/click_intercept/buildmode/BM) + return + +/datum/buildmode_mode/proc/get_button_iconstate() + return "buildmode_[key]" + +/datum/buildmode_mode/proc/show_help(mob/user) + CRASH("No help defined, yell at a coder") + to_chat(user, "There is no help defined for this mode, this is a bug.") + +/datum/buildmode_mode/proc/change_settings(mob/user) + to_chat(user, "There is no configuration available for this mode") + +/datum/buildmode_mode/proc/Reset() + deselect_region() + +/datum/buildmode_mode/proc/select_tile(turf/T, corner_to_select) + var/overlaystate + BM.holder.images -= preview + switch(corner_to_select) + if(AREASELECT_CORNERA) + overlaystate = "greenOverlay" + if(AREASELECT_CORNERB) + overlaystate = "blueOverlay" + preview += image('icons/turf/overlays.dmi', T, overlaystate) + BM.holder.images += preview + return T + +/datum/buildmode_mode/proc/highlight_region(region) + BM.holder.images -= preview + for(var/t in region) + preview += image('icons/turf/overlays.dmi', T, "redOverlay") + BM.holder.images += preview + +/datum/buildmode_mode/proc/deselect_region() + BM.holder.images -= preview + QDEL_LIST(preview) + cornerA = null + cornerB = null + +/datum/buildmode_mode/proc/handle_click(user, params, object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + if(use_corner_selection) + if(left_click) + if(!cornerA) + cornerA = select_tile(get_turf(object), AREASELECT_CORNERA) + return + else if(!cornerB) + cornerB = select_tile(get_turf(object), AREASELECT_CORNERB) + to_chat(user, "Region selected, if you're happy with your selection left click again, otherwise right click.") + return + if(processing_selection) + return + processing_selection = TRUE + handle_selected_region(user, params) + processing_selection = FALSE + deselect_region() + else if(cornerA || cornerB) + to_chat(user, "Region selection canceled!") + deselect_region() + +/datum/buildmode_mode/proc/handle_selected_region(mob/user, params) + return \ No newline at end of file diff --git a/code/modules/buildmode/buildmode.dm b/code/modules/buildmode/buildmode.dm new file mode 100644 index 00000000000..934eba8117c --- /dev/null +++ b/code/modules/buildmode/buildmode.dm @@ -0,0 +1,127 @@ +#define BM_SWITCHSTATE_NONE 0 +#define BM_SWITCHSTATE_MODE 1 +#define BM_SWITCHSTATE_DIR 2 + +/datum/click_intercept/buildmode + var/build_dir = SOUTH + var/datum/buildmode_mode/mode + + // SECTION UI + + // Switching management + var/switch_state = BM_SWITCHSTATE_NONE + var/switch_width = 5 + // modeswitch UI + var/obj/screen/buildmode/mode/modebutton + var/list/modeswitch_buttons = list() + // dirswitch UI + var/obj/screen/buildmode/bdir/dirbutton + var/list/dirswitch_buttons = list() + +/datum/click_intercept/buildmode/New() + mode = new /datum/buildmode_mode/basic(src) + . = ..() + mode.enter_mode(src) + +/datum/click_intercept/buildmode/Destroy() + close_switchstates() + QDEL_NULL(mode) + QDEL_LIST(modeswitch_buttons) + QDEL_LIST(dirswitch_buttons) + return ..() + +/datum/click_intercept/buildmode/create_buttons() + // keep a reference so we can update it upon mode switch + modebutton = new /obj/screen/buildmode/mode(src) + buttons += modebutton + buttons += new /obj/screen/buildmode/help(src) + // keep a reference so we can update it upon dir switch + dirbutton = new /obj/screen/buildmode/bdir(src) + buttons += dirbutton + buttons += new /obj/screen/buildmode/quit(src) + // build the list of modeswitching buttons + build_options_grid(subtypesof(/datum/buildmode_mode), modeswitch_buttons, /obj/screen/buildmode/modeswitch) + build_options_grid(list(SOUTH,EAST,WEST,NORTH,NORTHWEST), dirswitch_buttons, /obj/screen/buildmode/dirswitch) + +/datum/click_intercept/buildmode/proc/build_options_grid(list/elements, list/buttonslist, buttontype) + var/pos_idx = 0 + for(var/thing in elements) + var/x = pos_idx % switch_width + var/y = FLOOR(pos_idx / switch_width, 1) + var/obj/screen/buildmode/B = new buttontype(src, thing) + // extra .5 for a nice offset look + B.screen_loc = "NORTH-[(1 + 0.5 + y*1.5)],WEST+[0.5 + x*1.5]" + buttonslist += B + pos_idx++ + +/datum/click_intercept/buildmode/proc/close_switchstates() + switch(switch_state) + if(BM_SWITCHSTATE_MODE) + close_modeswitch() + if(BM_SWITCHSTATE_DIR) + close_dirswitch() + +/datum/click_intercept/buildmode/proc/toggle_modeswitch() + if(switch_state == BM_SWITCHSTATE_MODE) + close_modeswitch() + else + close_switchstates() + open_modeswitch() + +/datum/click_intercept/buildmode/proc/open_modeswitch() + switch_state = BM_SWITCHSTATE_MODE + holder.screen += modeswitch_buttons + +/datum/click_intercept/buildmode/proc/close_modeswitch() + switch_state = BM_SWITCHSTATE_NONE + holder.screen -= modeswitch_buttons + +/datum/click_intercept/buildmode/proc/toggle_dirswitch() + if(switch_state == BM_SWITCHSTATE_DIR) + close_dirswitch() + else + close_switchstates() + open_dirswitch() + +/datum/click_intercept/buildmode/proc/open_dirswitch() + switch_state = BM_SWITCHSTATE_DIR + holder.screen += dirswitch_buttons + +/datum/click_intercept/buildmode/proc/close_dirswitch() + switch_state = BM_SWITCHSTATE_NONE + holder.screen -= dirswitch_buttons + +/datum/click_intercept/buildmode/proc/change_mode(newmode) + mode.exit_mode(src) + QDEL_NULL(mode) + close_switchstates() + mode = new newmode(src) + mode.enter_mode(src) + modebutton.update_icon() + +/datum/click_intercept/buildmode/proc/change_dir(newdir) + build_dir = newdir + close_dirswitch() + dirbutton.update_icon() + return TRUE + +/datum/click_intercept/buildmode/InterceptClickOn(user, params, atom/object) + mode.handle_click(user, params, object) + +/proc/togglebuildmode(mob/M in player_list) + set name = "Toggle Build Mode" + set category = "Event" + + if(M.client) + if(istype(M.client.click_intercept, /datum/click_intercept/buildmode)) + var/datum/click_intercept/buildmode/B = M.client.click_intercept + B.quit() + log_admin("[key_name(usr)] has left build mode.") + else + new/datum/click_intercept/buildmode(M.client) + message_admins("[key_name_admin(usr)] has entered build mode.") + log_admin("[key_name(usr)] has entered build mode.") + +#undef BM_SWITCHSTATE_NONE +#undef BM_SWITCHSTATE_MODE +#undef BM_SWITCHSTATE_DIR \ No newline at end of file diff --git a/code/modules/buildmode/buttons.dm b/code/modules/buildmode/buttons.dm new file mode 100644 index 00000000000..c07be97a9eb --- /dev/null +++ b/code/modules/buildmode/buttons.dm @@ -0,0 +1,88 @@ +/obj/screen/buildmode + icon = 'icons/misc/buildmode.dmi' + var/datum/click_intercept/buildmode/bd + layer = HUD_LAYER_BUILDMODE + +/obj/screen/buildmode/New(bld) + bd = bld + return ..() + +/obj/screen/buildmode/Destroy() + bd = null + return ..() + +/obj/screen/buildmode/mode + name = "Toggle Mode" + icon_state = "buildmode_basic" + screen_loc = "NORTH,WEST" + +/obj/screen/buildmode/mode/Click(location, control, params) + var/list/pa = params2list(params) + + if(pa.Find("left")) + bd.toggle_modeswitch() + else if(pa.Find("right")) + bd.mode.change_settings(usr) + update_icon() + return TRUE + +/obj/screen/buildmode/mode/update_icon() + icon_state = bd.mode.get_button_iconstate() + +/obj/screen/buildmode/help + icon_state = "buildhelp" + screen_loc = "NORTH,WEST+1" + name = "Buildmode Help" + +/obj/screen/buildmode/help/Click() + bd.mode.show_help(usr) + return TRUE + +/obj/screen/buildmode/bdir + icon_state = "build" + screen_loc = "NORTH,WEST+2" + name = "Change Dir" + +/obj/screen/buildmode/bdir/update_icon() + dir = bd.build_dir + +/obj/screen/buildmode/bdir/Click() + bd.toggle_dirswitch() + update_icon() + return TRUE + +// used to switch between modes +/obj/screen/buildmode/modeswitch + var/datum/buildmode_mode/modetype + +/obj/screen/buildmode/modeswitch/New(bld, mt) + modetype = mt + icon_state = "buildmode_[initial(modetype.key)]" + name = initial(modetype.key) + return ..(bld) + +/obj/screen/buildmode/modeswitch/Click() + bd.change_mode(modetype) + return TRUE + +// used to switch between dirs +/obj/screen/buildmode/dirswitch + icon_state = "build" + +/obj/screen/buildmode/dirswitch/New(bld, newdir) + dir = newdir + name = dir2text(dir) + return ..(bld) + +/obj/screen/buildmode/dirswitch/Click() + bd.change_dir(dir) + return TRUE + +/obj/screen/buildmode/quit + icon_state = "buildquit" + screen_loc = "NORTH,WEST+3" + name = "Quit Buildmode" + +/obj/screen/buildmode/quit/Click() + bd.quit() + return TRUE \ No newline at end of file diff --git a/code/modules/buildmode/effects/line.dm b/code/modules/buildmode/effects/line.dm new file mode 100644 index 00000000000..4b59e8a673c --- /dev/null +++ b/code/modules/buildmode/effects/line.dm @@ -0,0 +1,28 @@ +/obj/effect/buildmode_line + var/image/I + var/client/cl + +/obj/effect/buildmode_line/New(client/C, atom/atom_a, atom/atom_b, linename) + name = linename + loc = get_turf(atom_a) + I = image('icons/misc/mark.dmi', src, "line", 19.0) + var/x_offset = ((atom_b.x * 32) + atom_b.pixel_x) - ((atom_a.x * 32) + atom_a.pixel_x) + var/y_offset = ((atom_b.y * 32) + atom_b.pixel_y) - ((atom_a.y * 32) + atom_a.pixel_y) + + var/matrix/mat = matrix() + mat.Translate(0, 16) + mat.Scale(1, sqrt((x_offset * x_offset) + (y_offset * y_offset)) / 32) + mat.Turn(90 - Atan2(x_offset, y_offset)) // So... You pass coords in order x,y to this version of atan2. It should be called acsc2. + mat.Translate(atom_a.pixel_x, atom_a.pixel_y) + + transform = mat + cl = C + cl.images += I + +/obj/effect/buildmode_line/Destroy() + if(I) + if(istype(cl)) + cl.images -= I + cl = null + QDEL_NULL(I) + return ..() \ No newline at end of file diff --git a/code/modules/buildmode/submodes/advanced.dm b/code/modules/buildmode/submodes/advanced.dm new file mode 100644 index 00000000000..fadd46753bb --- /dev/null +++ b/code/modules/buildmode/submodes/advanced.dm @@ -0,0 +1,59 @@ +/datum/buildmode_mode/advanced + key = "advanced" + var/objholder = null + +// FIXME: add logic which adds a button displaying the icon +// of the currently selected path + +/datum/buildmode_mode/advanced/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Right Mouse Button on buildmode button = Set object type") + to_chat(user, "Left Mouse Button + alt on turf/obj = Copy object type") + to_chat(user, "Left Mouse Button on turf/obj = Place objects") + to_chat(user, "Right Mouse Button = Delete objects") + to_chat(user, "") + to_chat(user, "Use the button in the upper left corner to") + to_chat(user, "change the direction of built objects.") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/advanced/change_settings(mob/user) + var/target_path = input(user,"Enter typepath:" ,"Typepath","/obj/structure/closet") + objholder = text2path(target_path) + if(!ispath(objholder)) + objholder = pick_closest_path(target_path) + if(!objholder) + alert("No path was selected") + return + else if(ispath(objholder, /area)) + objholder = null + alert("That path is not allowed.") + return + +/datum/buildmode_mode/advanced/handle_click(user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + var/alt_click = pa.Find("alt") + + if(left_click && alt_click) + if (isturf(object) || isobj(object) || ismob(object)) + objholder = object.type + to_chat(user, "[initial(object.name)] ([object.type]) selected.") + else + to_chat(user, "[initial(object.name)] is not a turf, object, or mob! Please select again.") + else if(left_click) + if(ispath(objholder,/turf)) + var/turf/T = get_turf(object) + log_admin("Build Mode: [key_name(user)] modified [T] ([T.x],[T.y],[T.z]) to [objholder]") + T.ChangeTurf(objholder) + else if(!isnull(objholder)) + var/obj/A = new objholder (get_turf(object)) + A.setDir(BM.build_dir) + log_admin("Build Mode: [key_name(user)] modified [A]'s ([A.x],[A.y],[A.z]) dir to [BM.build_dir]") + else + to_chat(user, "Select object type first.") + else if(right_click) + if(isobj(object)) + log_admin("Build Mode: [key_name(user)] deleted [object] at ([object.x],[object.y],[object.z])") + qdel(object) + \ No newline at end of file diff --git a/code/modules/buildmode/submodes/area_edit.dm b/code/modules/buildmode/submodes/area_edit.dm new file mode 100644 index 00000000000..d60950d294f --- /dev/null +++ b/code/modules/buildmode/submodes/area_edit.dm @@ -0,0 +1,59 @@ +/datum/buildmode_mode/area_edit + key = "areaedit" + var/area/storedarea + var/image/areaimage + +/datum/buildmode_mode/area_edit/New() + areaimage = image('icons/turf/areas.dmi', null, "yellow") + ..() + +/datum/buildmode_mode/area_edit/enter_mode(datum/click_intercept/buildmode/BM) + BM.holder.images += areaimage + +/datum/buildmode_mode/area_edit/exit_mode(datum/click_intercept/buildmode/BM) + areaimage.loc = null // de-color the area + BM.holder.images -= areaimage + return ..() + +/datum/buildmode_mode/area_edit/Destroy() + QDEL_NULL(areaimage) + return ..() + +/datum/buildmode_mode/area_edit/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on obj/turf/mob = Paint area") + to_chat(user, "Right Mouse Button on obj/turf/mob = Select area to paint") + to_chat(user, "Right Mouse Button on buildmode button = Create new area") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/area_edit/change_settings(mob/user) + var/target_path = input(user,"Enter typepath:", "Typepath", "/area") + var/areatype = text2path(target_path) + if(ispath(areatype,/area)) + var/areaname = input(user,"Enter area name:", "Area name", "Area") + if(!areaname || !length(areaname)) + return + storedarea = new areatype + storedarea.power_equip = 0 + storedarea.power_light = 0 + storedarea.power_environ = 0 + storedarea.always_unpowered = 0 + storedarea.name = areaname + areaimage.loc = storedarea // color our area + +/datum/buildmode_mode/area_edit/handle_click(user, params, object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + + if(left_click) + if(!storedarea) + to_chat(user, "Configure or select the area you want to paint first!") + return + var/turf/T = get_turf(object) + if(get_area(T) != storedarea) + storedarea.contents.Add(T) + else if(right_click) + var/turf/T = get_turf(object) + storedarea = get_area(T) + areaimage.loc = storedarea // color our area diff --git a/code/modules/buildmode/submodes/atmos.dm b/code/modules/buildmode/submodes/atmos.dm new file mode 100644 index 00000000000..f12fdb1eb1a --- /dev/null +++ b/code/modules/buildmode/submodes/atmos.dm @@ -0,0 +1,72 @@ +/datum/buildmode_mode/atmos + key = "atmos" + + use_corner_selection = TRUE + var/pressure = ONE_ATMOSPHERE + var/temperature = T20C + var/oxygen = O2STANDARD + var/nitrogen = N2STANDARD + var/plasma = 0 + var/cdiox = 0 + var/nitrox = 0 + + +/datum/buildmode_mode/atmos/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on turf = Select corner") + to_chat(user, "Left Mouse Button + Ctrl on turf = Set 'base atmos conditions' for unsimulated turfs in region") + to_chat(user, "Right Mouse Button on buildmode button = Adjust target atmos") + to_chat(user, "Notice: Starts out with standard breathable/liveable defaults") + to_chat(user, "***********************************************************") + +// FIXME this is a little tedious, something where you don't have to fill in each field would be cooler +// maybe some kind of stat panel thing? +/datum/buildmode_mode/atmos/change_settings(mob/user) + pressure = input(user, "Atmospheric Pressure", "Input", ONE_ATMOSPHERE) as num|null + temperature = input(user, "Temperature", "Input", T20C) as num|null + oxygen = input(user, "Oxygen ratio", "Input", O2STANDARD) as num|null + nitrogen = input(user, "Nitrogen ratio", "Input", N2STANDARD) as num|null + plasma = input(user, "Plasma ratio", "Input", 0) as num|null + cdiox = input(user, "CO2 ratio", "Input", 0) as num|null + nitrox = input(user, "N2O ratio", "Input", 0) as num|null + +/datum/buildmode_mode/atmos/proc/ppratio_to_moles(ppratio) + // ideal gas equation: Pressure * Volume = Moles * r * Temperature + // air datum fields are in moles, we have partial pressure ratios + // Moles = (Pressure * Volume) / (r * Temperature) + return ((ppratio * pressure) * CELL_VOLUME) / (temperature * R_IDEAL_GAS_EQUATION) + +/datum/buildmode_mode/atmos/handle_selected_region(mob/user, params) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/ctrl_click = pa.Find("ctrl") + if(left_click) //rectangular + for(var/turf/T in block(cornerA,cornerB)) + if(istype(T, /turf/simulated)) + // fill the turf with the appropriate gasses + // this feels slightly icky + var/turf/simulated/S = T + if(S.air) + S.air.temperature = temperature + S.air.oxygen = ppratio_to_moles(oxygen) + S.air.nitrogen = ppratio_to_moles(nitrogen) + S.air.toxins = ppratio_to_moles(plasma) + S.air.carbon_dioxide = ppratio_to_moles(cdiox) + S.air.trace_gases.Cut() + if(nitrox) + var/datum/gas/TG = new /datum/gas/sleeping_agent + TG.moles = ppratio_to_moles(nitrox) + S.air.trace_gases += TG + S.update_visuals() + S.air_update_turf() + else if(ctrl_click) // overwrite "default" unsimulated air + T.temperature = temperature + T.oxygen = ppratio_to_moles(oxygen) + T.nitrogen = ppratio_to_moles(nitrogen) + T.toxins = ppratio_to_moles(plasma) + T.carbon_dioxide = ppratio_to_moles(cdiox) + // no interface for trace gases on unsim turfs + T.air_update_turf() + + // admin log + log_admin("Build Mode: [key_name(user)] changed the atmos of region [COORD(cornerA)] to [COORD(cornerB)]. T: [temperature], P: [pressure], Ox: [oxygen]%, N2: [nitrogen]%, Plsma: [plasma]%, CO2: [cdiox]%, N2O: [nitrox]%. [ctrl_click ? "Overwrote base unsimulated turf gases." : ""]") \ No newline at end of file diff --git a/code/modules/buildmode/submodes/basic.dm b/code/modules/buildmode/submodes/basic.dm new file mode 100644 index 00000000000..1ca36920c64 --- /dev/null +++ b/code/modules/buildmode/submodes/basic.dm @@ -0,0 +1,50 @@ +/datum/buildmode_mode/basic + key = "basic" + +/datum/buildmode_mode/basic/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button = Construct / Upgrade") + to_chat(user, "Right Mouse Button = Deconstruct / Delete / Downgrade") + to_chat(user, "Left Mouse Button + ctrl = R-Window") + to_chat(user, "Left Mouse Button + alt = Airlock") + to_chat(user, "") + to_chat(user, "Use the button in the upper left corner to") + to_chat(user, "change the direction of built objects.") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/basic/handle_click(user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + var/ctrl_click = pa.Find("ctrl") + var/alt_click = pa.Find("alt") + + if(istype(object,/turf) && left_click && !alt_click && !ctrl_click) + var/turf/T = object + if(istype(object,/turf/space)) + T.ChangeTurf(/turf/simulated/floor/plasteel) + else if(istype(object,/turf/simulated/floor)) + T.ChangeTurf(/turf/simulated/wall) + else if(istype(object,/turf/simulated/wall)) + T.ChangeTurf(/turf/simulated/wall/r_wall) + log_admin("Build Mode: [key_name(user)] built [T] at ([T.x],[T.y],[T.z])") + else if(right_click) + log_admin("Build Mode: [key_name(user)] deleted [object] at ([object.x],[object.y],[object.z])") + if(istype(object,/turf/simulated/wall)) + var/turf/T = object + T.ChangeTurf(/turf/simulated/floor/plasteel) + else if(istype(object,/turf/simulated/floor)) + var/turf/T = object + T.ChangeTurf(/turf/space) + else if(istype(object,/turf/simulated/wall/r_wall)) + var/turf/T = object + T.ChangeTurf(/turf/simulated/wall) + else if(istype(object,/obj)) + qdel(object) + else if(istype(object,/turf) && alt_click && left_click) + log_admin("Build Mode: [key_name(user)] built an airlock at ([object.x],[object.y],[object.z])") + new/obj/machinery/door/airlock(get_turf(object)) + else if(istype(object,/turf) && ctrl_click && left_click) + var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object)) + WIN.setDir(BM.build_dir) + log_admin("Build Mode: [key_name(user)] built a window at ([object.x],[object.y],[object.z])") diff --git a/code/modules/buildmode/submodes/boom.dm b/code/modules/buildmode/submodes/boom.dm new file mode 100644 index 00000000000..271e5c01bc5 --- /dev/null +++ b/code/modules/buildmode/submodes/boom.dm @@ -0,0 +1,32 @@ +/datum/buildmode_mode/boom + key = "boom" + + var/devastation = -1 + var/heavy = -1 + var/light = -1 + var/flash = -1 + var/flames = -1 + +/datum/buildmode_mode/boom/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Mouse Button on obj = Kaboom") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/boom/change_settings(mob/user) + devastation = input("Range of total devastation. -1 to none", text("Input")) as num|null + if(devastation == null) devastation = -1 + heavy = input("Range of heavy impact. -1 to none", text("Input")) as num|null + if(heavy == null) heavy = -1 + light = input("Range of light impact. -1 to none", text("Input")) as num|null + if(light == null) light = -1 + flash = input("Range of flash. -1 to none", text("Input")) as num|null + if(flash == null) flash = -1 + flames = input("Range of flames. -1 to none", text("Input")) as num|null + if(flames == null) flames = -1 + +/datum/buildmode_mode/boom/handle_click(user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + + if(left_click) + explosion(object, devastation, heavy, light, flash, null, TRUE, flames) diff --git a/code/modules/buildmode/submodes/copy.dm b/code/modules/buildmode/submodes/copy.dm new file mode 100644 index 00000000000..48cbb2c3cf0 --- /dev/null +++ b/code/modules/buildmode/submodes/copy.dm @@ -0,0 +1,27 @@ +/datum/buildmode_mode/copy + key = "copy" + var/atom/movable/stored = null + +/datum/buildmode_mode/copy/exit_mode() + stored = null + return ..() + +/datum/buildmode_mode/copy/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on obj/turf/mob = Spawn a Copy of selected target") + to_chat(user, "Right Mouse Button on obj/mob = Select target to copy") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/copy/handle_click(user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + + if(left_click) + var/turf/T = get_turf(object) + if(stored) + DuplicateObject(stored, perfectcopy=1, sameloc=0,newloc=T) + else if(right_click) + if(ismovableatom(object)) // No copying turfs for now. + to_chat(user, "[object] set as template.") + stored = object diff --git a/code/modules/buildmode/submodes/fill.dm b/code/modules/buildmode/submodes/fill.dm new file mode 100644 index 00000000000..b143b2c9b90 --- /dev/null +++ b/code/modules/buildmode/submodes/fill.dm @@ -0,0 +1,49 @@ +/datum/buildmode_mode/fill + key = "fill" + + use_corner_selection = TRUE + var/objholder = null + +/datum/buildmode_mode/fill/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner") + to_chat(user, "Left Mouse Button + Alt on turf/obj/mob = Delete region") + to_chat(user, "Right Mouse Button on buildmode button = Select object type") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/fill/change_settings(mob/user) + var/target_path = input(user,"Enter typepath:" ,"Typepath","/obj/structure/closet") + objholder = text2path(target_path) + if(!ispath(objholder)) + objholder = pick_closest_path(target_path) + if(!objholder) + alert("No path has been selected.") + return + else if(ispath(objholder, /area)) + objholder = null + alert("Area paths are not supported for this mode, use the area edit mode instead.") + return + deselect_region() + +/datum/buildmode_mode/fill/handle_click(mob/user, params, obj/object) + if(isnull(objholder)) + to_chat(user, "Select an object type first.") + deselect_region() + return + ..() + +/datum/buildmode_mode/fill/handle_selected_region(mob/user, params) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/alt_click = pa.Find("alt") + + if(left_click) //rectangular + if(alt_click) + empty_region(block(cornerA,cornerB)) + else + for(var/turf/T in block(cornerA,cornerB)) + if(ispath(objholder,/turf)) + T.ChangeTurf(objholder) + else + var/obj/A = new objholder(T) + A.setDir(BM.build_dir) \ No newline at end of file diff --git a/code/modules/buildmode/submodes/link.dm b/code/modules/buildmode/submodes/link.dm new file mode 100644 index 00000000000..153b5a7698e --- /dev/null +++ b/code/modules/buildmode/submodes/link.dm @@ -0,0 +1,94 @@ +/datum/buildmode_mode/link + key = "link" + + var/list/link_lines = list() + var/obj/link_obj + + +/datum/buildmode_mode/link/proc/clear_lines() + QDEL_LIST(link_lines) + +/datum/buildmode_mode/link/proc/form_connection(atom/source, atom/dest, valid) + var/obj/effect/buildmode_line/L = new(BM.holder, source, dest, "[source.name] to [dest.name]") + L.color = valid ? "#339933" : "#993333" + link_lines += L + var/obj/effect/buildmode_line/L2 = new(BM.holder, dest, source, "[dest.name] to [source.name]") // Yes, reversed one so that you can see it source both sides. + L2.color = L.color + link_lines += L2 + +/datum/buildmode_mode/link/Destroy() + clear_lines() + link_obj = null + return ..() + +/datum/buildmode_mode/link/Reset() + clear_lines() + link_obj = null + ..() + +/datum/buildmode_mode/link/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on obj = Select button to link") + to_chat(user, "Right Mouse Button on obj = Link/unlink to selected button") + to_chat(user, "***********************************************************") + +// FIXME: this probably would work better with something component-based +/datum/buildmode_mode/link/handle_click(mob/user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + if(left_click && istype(object, /obj/machinery)) + link_obj = object + if(right_click && istype(object, /obj/machinery)) + if(istype(link_obj, /obj/machinery/door_control) && istype(object, /obj/machinery/door/airlock)) + var/obj/machinery/door_control/M = link_obj + var/obj/machinery/door/airlock/P = object + if(!M.id || M.id == "") + M.id = input(user, "Please select an ID for the button", "Buildmode", "") + if(!M.id || M.id == "") + goto(line_jump) + if(P.id_tag == M.id && (P in range(M.range, M)) && P.id_tag && P.id_tag != "") + P.id_tag = null + to_chat(user, "[P] unlinked.") + goto(line_jump) + if(!M.normaldoorcontrol) + if(link_lines.len && alert(user, "Warning: This will disable links to connected pod doors. Continue?", "Buildmode", "Yes", "No") == "No") + goto(line_jump) + M.normaldoorcontrol = 1 + if(P.id_tag && alert(user, "Warning: This will unlink something else from the door. Continue?", "Buildmode", "Yes", "No") == "No") + goto(line_jump) + P.id_tag = M.id + if(istype(link_obj, /obj/machinery/door_control) && istype(object, /obj/machinery/door/poddoor)) + var/obj/machinery/door_control/M = link_obj + var/obj/machinery/door/poddoor/P = object + if(!M.id || M.id == "") + M.id = input(user, "Please select an ID for the button", "Buildmode", "") + if(!M.id || M.id == "") + goto(line_jump) + if(P.id_tag == M.id && P.id_tag && P.id_tag != "") + P.id_tag = null + to_chat(user, "[P] unlinked.") + goto(line_jump) + if(M.normaldoorcontrol) + if(link_lines.len && alert(user, "Warning: This will disable links to connected airlocks. Continue?", "Buildmode", "Yes", "No") == "No") + goto(line_jump) + M.normaldoorcontrol = 0 + if(!M.id || M.id == "") + M.id = input(user, "Please select an ID for the button", "Buildmode", "") + if(!M.id || M.id == "") + goto(line_jump) + if(P.id_tag && P.id_tag != 1 && alert(user, "Warning: This will unlink something else from the door. Continue?", "Buildmode", "Yes", "No") == "No") + goto(line_jump) + P.id_tag = M.id + + line_jump // For the goto + clear_lines() + + if(istype(link_obj, /obj/machinery/door_control)) + var/obj/machinery/door_control/M = link_obj + for(var/obj/machinery/door/airlock/P in range(M.range,M)) + if(P.id_tag == M.id) + form_connection(M, P, M.normaldoorcontrol) + for(var/obj/machinery/door/poddoor/P in airlocks) + if(P.id_tag == M.id) + form_connection(M, P, !M.normaldoorcontrol) diff --git a/code/modules/buildmode/submodes/mapgen.dm b/code/modules/buildmode/submodes/mapgen.dm new file mode 100644 index 00000000000..22be429c2ce --- /dev/null +++ b/code/modules/buildmode/submodes/mapgen.dm @@ -0,0 +1,40 @@ +/datum/buildmode_mode/mapgen + key = "mapgen" + + use_corner_selection = TRUE + var/generator_path + +/datum/buildmode_mode/mapgen/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner") + to_chat(user, "Right Mouse Button on buildmode button = Select generator") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/mapgen/change_settings(mob/user) + var/list/gen_paths = subtypesof(/datum/mapGenerator) + + var/type = input(user,"Select Generator Type","Type") as null|anything in gen_paths + if(!type) return + + generator_path = type + deselect_region() + +/datum/buildmode_mode/mapgen/handle_click(mob/user, params, obj/object) + if(isnull(generator_path)) + to_chat(user, "Select generator type first.") + deselect_region() + return + ..() + +/datum/buildmode_mode/mapgen/handle_selected_region(mob/user, params) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + + if(left_click) //rectangular + if(cornerA && cornerB) + var/datum/mapGenerator/G = new generator_path + G.defineRegion(cornerA, cornerB, 1) + highlight_region(G.map) + var/confirm = alert("Are you sure you want run the map generator?", "Run generator", "Yes", "No") + if(confirm == "Yes") + G.generate() \ No newline at end of file diff --git a/code/modules/buildmode/submodes/save.dm b/code/modules/buildmode/submodes/save.dm new file mode 100644 index 00000000000..43a1f9af174 --- /dev/null +++ b/code/modules/buildmode/submodes/save.dm @@ -0,0 +1,28 @@ +/datum/buildmode_mode/save + key = "save" + + use_corner_selection = TRUE + var/use_json = TRUE + +/datum/buildmode_mode/save/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on turf/obj/mob = Select corner") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/save/change_settings(mob/user) + use_json = (alert("Would you like to use json (Default is \"Yes\")?",,"Yes","No") == "Yes") + +/datum/buildmode_mode/save/handle_selected_region(mob/user, params) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + if(left_click) + var/map_name = input(user, "Please select a name for your map", "Buildmode", "") + if(map_name == "") + return + var/map_flags = 0 + if(use_json) + map_flags = 32 // Magic number defined in `writer.dm` that I can't use directly + // because #defines are for some reason our coding standard + var/our_map = maploader.save_map(cornerA, cornerB, map_name, map_flags) + user << ftp(our_map) // send the map they've made! Or are stealing, whatever + to_chat(user, "Map saving complete! [our_map]") diff --git a/code/modules/buildmode/submodes/sdql.dm b/code/modules/buildmode/submodes/sdql.dm new file mode 100644 index 00000000000..104b7bd7af6 --- /dev/null +++ b/code/modules/buildmode/submodes/sdql.dm @@ -0,0 +1,12 @@ +// SKELETON CODE, not yet functional + +/datum/buildmode_mode/sdql + key = "sdql" + + var/sdql_command = "SELECT /turf IN" + +/datum/buildmode_mode/sdql/show_help(mob/user) + +/datum/buildmode_mode/sdql/change_settings(mob/user) + +/datum/buildmode_mode/sdql/handle_click(mob/user, params, obj/object) diff --git a/code/modules/buildmode/submodes/throwing.dm b/code/modules/buildmode/submodes/throwing.dm new file mode 100644 index 00000000000..24470a680b5 --- /dev/null +++ b/code/modules/buildmode/submodes/throwing.dm @@ -0,0 +1,25 @@ +/datum/buildmode_mode/throwing + key = "throw" + + var/atom/movable/throw_atom = null + +/datum/buildmode_mode/throwing/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on turf/obj/mob = Select") + to_chat(user, "Right Mouse Button on turf/obj/mob = Throw") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/throwing/handle_click(user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + + if(left_click) + if(isturf(object)) + return + throw_atom = object + to_chat(user, "Selected object '[throw_atom]'") + if(right_click) + if(throw_atom) + throw_atom.throw_at(object, 10, 1, user) + log_admin("Build Mode: [key_name(user)] threw [throw_atom] at [object] ([object.x],[object.y],[object.z])") diff --git a/code/modules/buildmode/submodes/variable_edit.dm b/code/modules/buildmode/submodes/variable_edit.dm new file mode 100644 index 00000000000..ebf0035bccf --- /dev/null +++ b/code/modules/buildmode/submodes/variable_edit.dm @@ -0,0 +1,55 @@ +/datum/buildmode_mode/varedit + key = "edit" + // Varedit mode + var/varholder = "name" + var/valueholder = "value" + +/datum/buildmode_mode/varedit/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Right Mouse Button on buildmode button = Select var(type) & value") + to_chat(user, "Left Mouse Button on turf/obj/mob = Set var(type) & value") + to_chat(user, "Right Mouse Button on turf/obj/mob = Reset var's value") + to_chat(user, "***********************************************************") + +// FIXME: This needs to use a standard var-editing interface instead of +// doing its own thing here +/datum/buildmode_mode/varedit/change_settings(mob/user) + var/temp_varname = input(user,"Enter variable name:", "Name", "name") + if(!vv_varname_lockcheck(temp_varname)) + return + + var/temp_value = user.client.vv_get_value() + if(isnull(temp_value["class"])) + Reset() + to_chat(user, "Variable unset.") + return + // we assign this once all user input is done, since things could get wonky otherwise + varholder = temp_varname + valueholder = temp_value["value"] + +/datum/buildmode_mode/varedit/handle_click(user, params, obj/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + + if(isnull(varholder)) + to_chat(user, "Choose a variable to modify first.") + return + if(left_click) + if(object.vars.Find(varholder)) + if(!object.vv_edit_var(varholder, valueholder)) + to_chat(user, "Your edit was rejected by [object].") + return + log_admin("Build Mode: [key_name(user)] modified [object.name]'s [varholder] to [valueholder]") + else + to_chat(user, "[initial(object.name)] does not have a var called '[varholder]'") + if(right_click) + if(object.vars.Find(varholder)) + var/reset_value = initial(object.vars[varholder]) + if(!object.vv_edit_var(varholder, reset_value)) + to_chat(user, "Your edit was rejected by [object].") + return + log_admin("Build Mode: [key_name(user)] modified [object.name]'s [varholder] to [reset_value]") + else + to_chat(user, "[initial(object.name)] does not have a var called '[varholder]'") + diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index cc54e5aec0f3eb56aae5642f0ac167280a1674c1..f0de428b6c55b37d8ad59cac0e9e5064e2d64ad7 100644 GIT binary patch delta 1600 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 Rv7}pPfQBkkrCP~0;(xpH8khh8 literal 5340 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