diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index e4a3ff1f9a..f034228bb9 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -7,10 +7,9 @@ SUBSYSTEM_DEF(shuttle) flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK runlevels = RUNLEVEL_SETUP | RUNLEVEL_GAME - var/obj/machinery/shuttle_manipulator/manipulator - var/list/mobile = list() var/list/stationary = list() + var/list/beacons = list() var/list/transit = list() var/list/transit_requesters = list() @@ -59,6 +58,15 @@ SUBSYSTEM_DEF(shuttle) var/realtimeofstart = 0 + var/datum/map_template/shuttle/selected + + var/obj/docking_port/mobile/existing_shuttle + + var/obj/docking_port/mobile/preview_shuttle + var/datum/map_template/shuttle/preview_template + + var/datum/turf_reservation/preview_reservation + /datum/controller/subsystem/shuttle/Initialize(timeofday) ordernum = rand(1, 9000) @@ -82,9 +90,6 @@ SUBSYSTEM_DEF(shuttle) return ..() /datum/controller/subsystem/shuttle/proc/initial_load() - if(!istype(manipulator)) - CRASH("No shuttle manipulator found.") - for(var/s in stationary) var/obj/docking_port/stationary/S = s S.load_roundstart() @@ -148,11 +153,13 @@ SUBSYSTEM_DEF(shuttle) ++alive var/total = GLOB.joined_player_list.len + if(total <= 0) + return //no players no autoevac if(alive / total <= threshold) - var/msg = "Automatically dispatching shuttle due to crew death." + var/msg = "Automatically dispatching emergency shuttle due to crew death." message_admins(msg) - log_game("[msg] Alive: [alive], Roundstart: [total], Threshold: [threshold]") + log_shuttle("[msg] Alive: [alive], Roundstart: [total], Threshold: [threshold]") emergencyNoRecall = TRUE priority_announce("Catastrophic casualties detected: crisis shuttle protocols activated - jamming recall signals across all frequencies.") if(emergency.timeLeft(1) > emergencyCallTime * 0.4) @@ -177,6 +184,34 @@ SUBSYSTEM_DEF(shuttle) return S WARNING("couldn't find dock with id: [id]") +/datum/controller/subsystem/shuttle/proc/canEvac(mob/user) + var/srd = CONFIG_GET(number/shuttle_refuel_delay) + if(world.time - SSticker.round_start_time < srd) + to_chat(user, "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.") + return FALSE + + switch(emergency.mode) + if(SHUTTLE_RECALL) + to_chat(user, "The emergency shuttle may not be called while returning to CentCom.") + return FALSE + if(SHUTTLE_CALL) + to_chat(user, "The emergency shuttle is already on its way.") + return FALSE + if(SHUTTLE_DOCKED) + to_chat(user, "The emergency shuttle is already here.") + return FALSE + if(SHUTTLE_IGNITING) + to_chat(user, "The emergency shuttle is firing its engines to leave.") + return FALSE + if(SHUTTLE_ESCAPE) + to_chat(user, "The emergency shuttle is moving away to a safe distance.") + return FALSE + if(SHUTTLE_STRANDED) + to_chat(user, "The emergency shuttle has been disabled by CentCom.") + return FALSE + + return TRUE + /datum/controller/subsystem/shuttle/proc/requestEvac(mob/user, call_reason) if(!emergency) WARNING("requestEvac(): There is no emergency shuttle, but the \ @@ -190,35 +225,14 @@ SUBSYSTEM_DEF(shuttle) manually, and then calling register() on the mobile docking port. \ Good luck.") emergency = backup_shuttle - var/srd = CONFIG_GET(number/shuttle_refuel_delay) - if(world.time - SSticker.round_start_time < srd) - to_chat(user, "The emergency shuttle is refueling. Please wait [DisplayTimeText(srd - (world.time - SSticker.round_start_time))] before trying again.") - return - switch(emergency.mode) - if(SHUTTLE_RECALL) - to_chat(user, "The emergency shuttle may not be called while returning to CentCom.") - return - if(SHUTTLE_CALL) - to_chat(user, "The emergency shuttle is already on its way.") - return - if(SHUTTLE_DOCKED) - to_chat(user, "The emergency shuttle is already here.") - return - if(SHUTTLE_IGNITING) - to_chat(user, "The emergency shuttle is firing its engines to leave.") - return - if(SHUTTLE_ESCAPE) - to_chat(user, "The emergency shuttle is moving away to a safe distance.") - return - if(SHUTTLE_STRANDED) - to_chat(user, "The emergency shuttle has been disabled by CentCom.") - return + if(!canEvac(user)) + return call_reason = trim(html_encode(call_reason)) if(length(call_reason) < CALL_SHUTTLE_REASON_LENGTH && seclevel2num(get_security_level()) > SEC_LEVEL_GREEN) - to_chat(user, "You must provide a reason.") + to_chat(user, "You must provide a reason.") return var/area/signal_origin = get_area(user) @@ -240,11 +254,11 @@ SUBSYSTEM_DEF(shuttle) var/area/A = get_area(user) - log_game("[key_name(user)] has called the shuttle.") - deadchat_broadcast("[user.real_name] has called the shuttle at [A.name].", user) + log_shuttle("[key_name(user)] has called the emergency shuttle.") + deadchat_broadcast(" has called the shuttle at [A.name].", "[user.real_name]", user, message_type=DEADCHAT_ANNOUNCEMENT) if(call_reason) SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]") - log_game("Shuttle call reason: [call_reason]") + log_shuttle("Shuttle call reason: [call_reason]") message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)") /datum/controller/subsystem/shuttle/proc/centcom_recall(old_timer, admiral_message) @@ -277,7 +291,7 @@ SUBSYSTEM_DEF(shuttle) /datum/controller/subsystem/shuttle/proc/cancelEvac(mob/user) if(canRecall()) emergency.cancel(get_area(user)) - log_game("[key_name(user)] has recalled the shuttle.") + log_shuttle("[key_name(user)] has recalled the shuttle.") message_admins("[ADMIN_LOOKUPFLW(user)] has recalled the shuttle.") deadchat_broadcast("[user.real_name] has recalled the shuttle from [get_area_name(user, TRUE)].", user) return 1 @@ -327,7 +341,7 @@ SUBSYSTEM_DEF(shuttle) if(callShuttle) if(EMERGENCY_IDLE_OR_RECALLED) emergency.request(null, set_coefficient = 2.5) - log_game("There is no means of calling the shuttle anymore. Shuttle automatically called.") + log_shuttle("There is no means of calling the emergency shuttle anymore. Shuttle automatically called.") message_admins("All the communications consoles were destroyed and all AIs are inactive. Shuttle called.") /datum/controller/subsystem/shuttle/proc/registerHostileEnvironment(datum/bad) @@ -380,7 +394,7 @@ SUBSYSTEM_DEF(shuttle) emergency.setTimer(emergencyDockTime) priority_announce("Hostile environment resolved. \ You have 3 minutes to board the Emergency Shuttle.", - null, "shuttledock", "Priority") + null, 'sound/ai/shuttledock.ogg', "Priority") //try to move/request to dockHome if possible, otherwise dockAway. Mainly used for admin buttons /datum/controller/subsystem/shuttle/proc/toggleShuttle(shuttleId, dockHome, dockAway, timed) @@ -565,6 +579,14 @@ SUBSYSTEM_DEF(shuttle) shuttle_purchased = SSshuttle.shuttle_purchased lockdown = SSshuttle.lockdown + selected = SSshuttle.selected + + existing_shuttle = SSshuttle.existing_shuttle + + preview_shuttle = SSshuttle.preview_shuttle + preview_template = SSshuttle.preview_template + + preview_reservation = SSshuttle.preview_reservation /datum/controller/subsystem/shuttle/proc/is_in_shuttle_bounds(atom/A) var/area/current = get_area(A) @@ -647,3 +669,252 @@ SUBSYSTEM_DEF(shuttle) message_admins("Round end vote passed. Shuttle has been auto-called.") emergencyNoRecall = TRUE endvote_passed = TRUE + +/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port) + // Check for an existing preview + if(preview_shuttle && (loading_template != preview_template)) + preview_shuttle.jumpToNullSpace() + preview_shuttle = null + preview_template = null + QDEL_NULL(preview_reservation) + + if(!preview_shuttle) + if(load_template(loading_template)) + preview_shuttle.linkup(loading_template, destination_port) + preview_template = loading_template + + // get the existing shuttle information, if any + var/timer = 0 + var/mode = SHUTTLE_IDLE + var/obj/docking_port/stationary/D + + if(istype(destination_port)) + D = destination_port + else if(existing_shuttle) + timer = existing_shuttle.timer + mode = existing_shuttle.mode + D = existing_shuttle.get_docked() + + if(!D) + D = generate_transit_dock(preview_shuttle) + + if(!D) + CRASH("No dock found for preview shuttle ([preview_template.name]), aborting.") + + var/result = preview_shuttle.canDock(D) + // truthy value means that it cannot dock for some reason + // but we can ignore the someone else docked error because we'll + // be moving into their place shortly + if((result != SHUTTLE_CAN_DOCK) && (result != SHUTTLE_SOMEONE_ELSE_DOCKED)) + WARNING("Template shuttle [preview_shuttle] cannot dock at [D] ([result]).") + return + + if(existing_shuttle) + existing_shuttle.jumpToNullSpace() + + var/list/force_memory = preview_shuttle.movement_force + preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0) + preview_shuttle.initiate_docking(D) + preview_shuttle.movement_force = force_memory + + . = preview_shuttle + + // Shuttle state involves a mode and a timer based on world.time, so + // plugging the existing shuttles old values in works fine. + preview_shuttle.timer = timer + preview_shuttle.mode = mode + + preview_shuttle.register() + + // TODO indicate to the user that success happened, rather than just + // blanking the modification tab + preview_shuttle = null + preview_template = null + existing_shuttle = null + selected = null + QDEL_NULL(preview_reservation) + +/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/S) + . = FALSE + // load shuttle template, centred at shuttle import landmark, + preview_reservation = SSmapping.RequestBlockReservation(S.width, S.height, SSmapping.transit.z_value, /datum/turf_reservation/transit) + if(!preview_reservation) + CRASH("failed to reserve an area for shuttle template loading") + var/turf/BL = TURF_FROM_COORDS_LIST(preview_reservation.bottom_left_coords) + S.load(BL, centered = FALSE, register = FALSE) + + var/affected = S.get_affected_turfs(BL, centered=FALSE) + + var/found = 0 + // Search the turfs for docking ports + // - We need to find the mobile docking port because that is the heart of + // the shuttle. + // - We need to check that no additional ports have slipped in from the + // template, because that causes unintended behaviour. + for(var/T in affected) + for(var/obj/docking_port/P in T) + if(istype(P, /obj/docking_port/mobile)) + found++ + if(found > 1) + qdel(P, force=TRUE) + log_world("Map warning: Shuttle Template [S.mappath] has multiple mobile docking ports.") + else + preview_shuttle = P + if(istype(P, /obj/docking_port/stationary)) + log_world("Map warning: Shuttle Template [S.mappath] has a stationary docking port.") + if(!found) + var/msg = "load_template(): Shuttle Template [S.mappath] has no mobile docking port. Aborting import." + for(var/T in affected) + var/turf/T0 = T + T0.empty() + + message_admins(msg) + WARNING(msg) + return + //Everything fine + S.post_load(preview_shuttle) + return TRUE + +/datum/controller/subsystem/shuttle/proc/unload_preview() + if(preview_shuttle) + preview_shuttle.jumpToNullSpace() + preview_shuttle = null + + +/datum/controller/subsystem/shuttle/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ShuttleManipulator", name, 800, 600, master_ui, state) + ui.open() + + +/datum/controller/subsystem/shuttle/ui_data(mob/user) + var/list/data = list() + data["tabs"] = list("Status", "Templates", "Modification") + + // Templates panel + data["templates"] = list() + var/list/templates = data["templates"] + data["templates_tabs"] = list() + data["selected"] = list() + + for(var/shuttle_id in SSmapping.shuttle_templates) + var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] + + if(!templates[S.port_id]) + data["templates_tabs"] += S.port_id + templates[S.port_id] = list( + "port_id" = S.port_id, + "templates" = list()) + + var/list/L = list() + L["name"] = S.name + L["shuttle_id"] = S.shuttle_id + L["port_id"] = S.port_id + L["description"] = S.description + L["admin_notes"] = S.admin_notes + + if(selected == S) + data["selected"] = L + + templates[S.port_id]["templates"] += list(L) + + data["templates_tabs"] = sortList(data["templates_tabs"]) + + data["existing_shuttle"] = null + + // Status panel + data["shuttles"] = list() + for(var/i in mobile) + var/obj/docking_port/mobile/M = i + var/timeleft = M.timeLeft(1) + var/list/L = list() + L["name"] = M.name + L["id"] = M.id + L["timer"] = M.timer + L["timeleft"] = M.getTimerStr() + if (timeleft > 1 HOURS) + L["timeleft"] = "Infinity" + L["can_fast_travel"] = M.timer && timeleft >= 50 + L["can_fly"] = TRUE + if(istype(M, /obj/docking_port/mobile/emergency)) + L["can_fly"] = FALSE + else if(!M.destination) + L["can_fast_travel"] = FALSE + if (M.mode != SHUTTLE_IDLE) + L["mode"] = capitalize(M.mode) + L["status"] = M.getDbgStatusText() + if(M == existing_shuttle) + data["existing_shuttle"] = L + + data["shuttles"] += list(L) + + return data + +/datum/controller/subsystem/shuttle/ui_act(action, params) + if(..()) + return + + var/mob/user = usr + + // Preload some common parameters + var/shuttle_id = params["shuttle_id"] + var/datum/map_template/shuttle/S = SSmapping.shuttle_templates[shuttle_id] + + switch(action) + if("select_template") + if(S) + existing_shuttle = getShuttle(S.port_id) + selected = S + . = TRUE + if("jump_to") + if(params["type"] == "mobile") + for(var/i in mobile) + var/obj/docking_port/mobile/M = i + if(M.id == params["id"]) + user.forceMove(get_turf(M)) + . = TRUE + break + + if("fly") + for(var/i in mobile) + var/obj/docking_port/mobile/M = i + if(M.id == params["id"]) + . = TRUE + M.admin_fly_shuttle(user) + break + + if("fast_travel") + for(var/i in mobile) + var/obj/docking_port/mobile/M = i + if(M.id == params["id"] && M.timer && M.timeLeft(1) >= 50) + M.setTimer(50) + . = TRUE + message_admins("[key_name_admin(usr)] fast travelled [M]") + log_admin("[key_name(usr)] fast travelled [M]") + SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[M.name]") + break + + if("preview") + if(S) + . = TRUE + unload_preview() + load_template(S) + if(preview_shuttle) + preview_template = S + user.forceMove(get_turf(preview_shuttle)) + if("load") + if(existing_shuttle == backup_shuttle) + // TODO make the load button disabled + WARNING("The shuttle that the selected shuttle will replace \ + is the backup shuttle. Backup shuttle is required to be \ + intact for round sanity.") + else if(S) + . = TRUE + // If successful, returns the mobile docking port + var/obj/docking_port/mobile/mdp = action_load(S) + if(mdp) + user.forceMove(get_turf(mdp)) + message_admins("[key_name_admin(usr)] loaded [mdp] with the shuttle manipulator.") + log_admin("[key_name(usr)] loaded [mdp] with the shuttle manipulator.") + SSblackbox.record_feedback("text", "shuttle_manipulator", 1, "[mdp.name]") diff --git a/code/datums/components/crafting/craft.dm b/code/datums/components/crafting/crafting.dm similarity index 82% rename from code/datums/components/crafting/craft.dm rename to code/datums/components/crafting/crafting.dm index 8ebd52522a..b9a1b5ec3b 100644 --- a/code/datums/components/crafting/craft.dm +++ b/code/datums/components/crafting/crafting.dm @@ -1,21 +1,18 @@ /datum/component/personal_crafting/Initialize() - if(!ismob(parent)) - return COMPONENT_INCOMPATIBLE - RegisterSignal(parent, COMSIG_MOB_CLIENT_LOGIN, .proc/create_mob_button) + if(ismob(parent)) + RegisterSignal(parent, COMSIG_MOB_CLIENT_LOGIN, .proc/create_mob_button) /datum/component/personal_crafting/proc/create_mob_button(mob/user, client/CL) var/datum/hud/H = user.hud_used var/obj/screen/craft/C = new() C.icon = H.ui_style H.static_inventory += C - if(!CL.prefs.widescreenpref) - C.screen_loc = ui_boxcraft CL.screen += C RegisterSignal(C, COMSIG_CLICK, .proc/component_ui_interact) /datum/component/personal_crafting var/busy - var/viewing_category = 1 + var/viewing_category = 1 //typical powergamer starting on the Weapons tab var/viewing_subcategory = 1 var/list/categories = list( CAT_WEAPONRY = list( @@ -58,9 +55,6 @@ var/display_craftable_only = FALSE var/display_compact = TRUE - - - /* This is what procs do: get_environment - gets a list of things accessable for crafting by user get_surroundings - takes a list of things and makes a list of key-types to values-amounts of said type in the list @@ -70,8 +64,6 @@ del_reqs - takes recipe and a user, loops over the recipes reqs var and tries to find everything in the list make by get_environment and delete it/add to parts list, then returns the said list */ - - /** * Check that the contents of the recipe meet the requirements. * @@ -79,7 +71,7 @@ * R: The /datum/crafting_recipe being attempted. * contents: List of items to search for R's reqs. */ -/datum/component/personal_crafting/proc/check_contents(mob/user, datum/crafting_recipe/R, list/contents) +/datum/component/personal_crafting/proc/check_contents(atom/a, datum/crafting_recipe/R, list/contents) var/list/item_instances = contents["instances"] contents = contents["other"] @@ -91,7 +83,7 @@ var/needed_amount = R.reqs[requirement_path] for(var/content_item_path in contents) // Right path and not blacklisted - if(!ispath(content_item_path, requirement_path) || R.blacklist.Find(requirement_path)) + if(!ispath(content_item_path, requirement_path) || R.blacklist.Find(content_item_path)) continue needed_amount -= contents[content_item_path] @@ -113,32 +105,25 @@ if(contents[requirement_path] < R.chem_catalysts[requirement_path]) return FALSE - return R.check_requirements(user, requirements_list) + return R.check_requirements(a, requirements_list) -/datum/component/personal_crafting/proc/get_environment(mob/user) +/datum/component/personal_crafting/proc/get_environment(atom/a, list/blacklist = null, radius_range = 1) . = list() - for(var/obj/item/I in user.held_items) - . += I - if(!isturf(user.loc)) - return - var/list/L = block(get_step(user, SOUTHWEST), get_step(user, NORTHEAST)) - for(var/A in L) - var/turf/T = A - if(T.Adjacent(user)) - for(var/B in T) - var/atom/movable/AM = B - if(AM.flags_1 & HOLOGRAM_1) - continue - . += AM - for(var/slot in list(SLOT_R_STORE, SLOT_L_STORE)) - . += user.get_item_by_slot(slot) -/datum/component/personal_crafting/proc/get_surroundings(mob/user) + if(!isturf(a.loc)) + return + + for(var/atom/movable/AM in range(radius_range, a)) + if(AM.flags_1 & HOLOGRAM_1) + continue + . += AM + +/datum/component/personal_crafting/proc/get_surroundings(atom/a) . = list() .["tool_behaviour"] = list() .["other"] = list() .["instances"] = list() - for(var/obj/item/I in get_environment(user)) + for(var/obj/item/I in get_environment(a)) if(I.flags_1 & HOLOGRAM_1) continue if(.["instances"][I.type]) @@ -159,13 +144,13 @@ .["other"][A.type] += A.volume .["other"][I.type] += 1 -/datum/component/personal_crafting/proc/check_tools(mob/user, datum/crafting_recipe/R, list/contents) +/datum/component/personal_crafting/proc/check_tools(atom/a, datum/crafting_recipe/R, list/contents) if(!R.tools.len) return TRUE var/list/possible_tools = list() var/list/present_qualities = list() present_qualities |= contents["tool_behaviour"] - for(var/obj/item/I in user.contents) + for(var/obj/item/I in a.contents) if(istype(I, /obj/item/storage)) for(var/obj/item/SI in I.contents) possible_tools += SI.type @@ -190,43 +175,28 @@ return FALSE return TRUE -/datum/component/personal_crafting/proc/construct_item(mob/user, datum/crafting_recipe/R) - var/list/contents = get_surroundings(user) - var/send_feedback = TRUE - if(check_contents(user, R, contents)) - if(check_tools(user, R, contents)) - if(do_after(user, R.time, target = user)) - contents = get_surroundings(user) - if(!check_contents(user, R, contents)) - return ", missing component." - if(!check_tools(user, R, contents)) - return ", missing tool." - var/list/parts = del_reqs(R, user) - var/atom/movable/I = new R.result (get_turf(user.loc)) - I.CheckParts(parts, R) - if(isitem(I)) - if(isfood(I)) - var/obj/item/reagent_containers/food/food_result = I - var/total_quality = 0 - var/total_items = 0 - for(var/obj/item/ingredient in parts) - var/obj/item/reagent_containers/food/food_ingredient = ingredient - total_items += 1 - total_quality += food_ingredient.food_quality - if(total_items == 0) - food_result.adjust_food_quality(50) - else - food_result.adjust_food_quality(total_quality / total_items) - user.put_in_hands(I) - if(send_feedback) - SSblackbox.record_feedback("tally", "object_crafted", 1, I.type) - log_craft("[I] crafted by [user] at [loc_name(I.loc)]") - return FALSE - return "." +/datum/component/personal_crafting/proc/construct_item(atom/a, datum/crafting_recipe/R) + var/list/contents = get_surroundings(a) + var/send_feedback = 1 + if(check_contents(a, R, contents)) + if(check_tools(a, R, contents)) + //If we're a mob we'll try a do_after; non mobs will instead instantly construct the item + if(ismob(a) && !do_after(a, R.time, target = a)) + return "." + contents = get_surroundings(a) + if(!check_contents(a, R, contents)) + return ", missing component." + if(!check_tools(a, R, contents)) + return ", missing tool." + var/list/parts = del_reqs(R, a) + var/atom/movable/I = new R.result (get_turf(a.loc)) + I.CheckParts(parts, R) + if(send_feedback) + SSblackbox.record_feedback("tally", "object_crafted", 1, I.type) + return I //Send the item back to whatever called this proc so it can handle whatever it wants to do with the new item return ", missing tool." return ", missing component." - /*Del reqs works like this: Loop over reqs var of the recipe @@ -251,7 +221,7 @@ del_reqs return the list of parts resulting object will receive as argument of CheckParts proc, on the atom level it will add them all to the contents, on all other levels it calls ..() and does whatever is needed afterwards but from contents list already */ -/datum/component/personal_crafting/proc/del_reqs(datum/crafting_recipe/R, mob/user) +/datum/component/personal_crafting/proc/del_reqs(datum/crafting_recipe/R, atom/a) var/list/surroundings var/list/Deletion = list() . = list() @@ -260,7 +230,7 @@ main_loop: for(var/A in R.reqs) amt = R.reqs[A] - surroundings = get_environment(user) + surroundings = get_environment(a, R.blacklist) surroundings -= Deletion if(ispath(A, /datum/reagent)) var/datum/reagent/RG = new A @@ -353,6 +323,7 @@ if(user == parent) ui_interact(user) +//For the UI related things we're going to assume the user is a mob rather than typesetting it to an atom as the UI isn't generated if the parent is an atom /datum/component/personal_crafting/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_turf_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) @@ -362,10 +333,9 @@ cur_subcategory = subcats[1] else cur_subcategory = CAT_NONE - ui = new(user, src, ui_key, "personal_crafting", "Crafting Menu", 700, 800, master_ui, state) + ui = new(user, src, ui_key, "PersonalCrafting", "Crafting Menu", 700, 800, master_ui, state) ui.open() - /datum/component/personal_crafting/ui_data(mob/user) var/list/data = list() data["busy"] = busy @@ -417,23 +387,24 @@ data["crafting_recipes"] = crafting_recipes return data - /datum/component/personal_crafting/ui_act(action, params) if(..()) return switch(action) if("make") + var/mob/user = usr var/datum/crafting_recipe/TR = locate(params["recipe"]) in GLOB.crafting_recipes - ui_interact(usr) - if(busy) - to_chat(usr, "You are already making something!") - return busy = TRUE - var/fail_msg = construct_item(usr, TR) - if(!fail_msg) - to_chat(usr, "[TR.name] constructed.") + ui_interact(user) + var/atom/movable/result = construct_item(user, TR) + if(!istext(result)) //We made an item and didn't get a fail message + if(ismob(user) && isitem(result)) //In case the user is actually possessing a non mob like a machine + user.put_in_hands(result) + else + result.forceMove(user.drop_location()) + to_chat(user, "[TR.name] constructed.") else - to_chat(usr, "Construction failed[fail_msg]") + to_chat(user, "Construction failed[result]") busy = FALSE if("toggle_recipes") display_craftable_only = !display_craftable_only diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm new file mode 100644 index 0000000000..200ec2b956 --- /dev/null +++ b/code/datums/components/gps.dm @@ -0,0 +1,153 @@ +///Global GPS_list. All GPS components get saved in here for easy reference. +GLOBAL_LIST_EMPTY(GPS_list) +///GPS component. Atoms that have this show up on gps. Pretty simple stuff. +/datum/component/gps + var/gpstag = "COM0" + var/tracking = TRUE + var/emped = FALSE + +/datum/component/gps/Initialize(_gpstag = "COM0") + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + gpstag = _gpstag + GLOB.GPS_list += src + +/datum/component/gps/Destroy() + GLOB.GPS_list -= src + return ..() + +///GPS component subtype. Only gps/item's can be used to open the UI. +/datum/component/gps/item + var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user. + var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown + +/datum/component/gps/item/Initialize(_gpstag = "COM0", emp_proof = FALSE) + . = ..() + if(. == COMPONENT_INCOMPATIBLE || !isitem(parent)) + return COMPONENT_INCOMPATIBLE + var/atom/A = parent + A.add_overlay("working") + A.name = "[initial(A.name)] ([gpstag])" + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/interact) + if(!emp_proof) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp_act) + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine) + RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_AltClick) + +///Called on COMSIG_ITEM_ATTACK_SELF +/datum/component/gps/item/proc/interact(datum/source, mob/user) + if(user) + ui_interact(user) + +///Called on COMSIG_PARENT_EXAMINE +/datum/component/gps/item/proc/on_examine(datum/source, mob/user, list/examine_list) + examine_list += "Alt-click to switch it [tracking ? "off":"on"]." + +///Called on COMSIG_ATOM_EMP_ACT +/datum/component/gps/item/proc/on_emp_act(datum/source, severity) + emped = TRUE + var/atom/A = parent + A.cut_overlay("working") + A.add_overlay("emp") + addtimer(CALLBACK(src, .proc/reboot), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early + SStgui.close_uis(src) //Close the UI control if it is open. + +///Restarts the GPS after getting turned off by an EMP. +/datum/component/gps/item/proc/reboot() + emped = FALSE + var/atom/A = parent + A.cut_overlay("emp") + A.add_overlay("working") + +///Calls toggletracking +/datum/component/gps/item/proc/on_AltClick(datum/source, mob/user) + toggletracking(user) + +///Toggles the tracking for the gps +/datum/component/gps/item/proc/toggletracking(mob/user) + if(!user.canUseTopic(parent, BE_CLOSE)) + return //user not valid to use gps + if(emped) + to_chat(user, "It's busted!") + return + var/atom/A = parent + if(tracking) + A.cut_overlay("working") + to_chat(user, "[parent] is no longer tracking, or visible to other GPS devices.") + tracking = FALSE + else + A.add_overlay("working") + to_chat(user, "[parent] is now tracking, and visible to other GPS devices.") + tracking = TRUE + +/datum/component/gps/item/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. + if(emped) + to_chat(user, "[parent] fizzles weakly.") + return + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + // Variable window height, depending on how many GPS units there are + // to show, clamped to relatively safe range. + var/gps_window_height = clamp(325 + GLOB.GPS_list.len * 14, 325, 700) + ui = new(user, src, ui_key, "Gps", "Global Positioning System", 470, gps_window_height, master_ui, state) //width, height + ui.open() + + ui.set_autoupdate(state = updating) + +/datum/component/gps/item/ui_data(mob/user) + var/list/data = list() + data["power"] = tracking + data["tag"] = gpstag + data["updating"] = updating + data["globalmode"] = global_mode + if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed + return data + + var/turf/curr = get_turf(parent) + data["currentArea"] = "[get_area_name(curr, TRUE)]" + data["currentCoords"] = "[curr.x], [curr.y], [curr.z]" + + var/list/signals = list() + data["signals"] = list() + + for(var/gps in GLOB.GPS_list) + var/datum/component/gps/G = gps + if(G.emped || !G.tracking || G == src) + continue + var/turf/pos = get_turf(G.parent) + if(!pos || !global_mode && pos.z != curr.z) + continue + var/list/signal = list() + signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS + signal["coords"] = "[pos.x], [pos.y], [pos.z]" + if(pos.z == curr.z) //Distance/Direction calculations for same z-level only + signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs + signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision. + signals += list(signal) //Add this signal to the list of signals + data["signals"] = signals + return data + +/datum/component/gps/item/ui_act(action, params) + if(..()) + return + switch(action) + if("rename") + var/atom/parentasatom = parent + var/a = stripped_input(usr, "Please enter desired tag.", parentasatom.name, gpstag, 20) + + if (!a) + return + + gpstag = a + . = TRUE + parentasatom.name = "global positioning system ([gpstag])" + + if("power") + toggletracking(usr) + . = TRUE + if("updating") + updating = !updating + . = TRUE + if("globalmode") + global_mode = !global_mode + . = TRUE diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index dde8961482..7242b47698 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -140,9 +140,8 @@ GLOBAL_LIST_EMPTY(uplinks) active = TRUE ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "uplink", name, 620, 580, master_ui, state) + ui = new(user, src, ui_key, "Uplink", name, 620, 580, master_ui, state) ui.set_autoupdate(FALSE) // This UI is only ever opened by one person, and never is updated outside of user input. - ui.set_style("syndicate") ui.open() /datum/component/uplink/ui_host(mob/user) @@ -157,8 +156,7 @@ GLOBAL_LIST_EMPTY(uplinks) var/list/data = list() data["telecrystals"] = telecrystals data["lockable"] = lockable - data["compact_mode"] = compact_mode - + data["compactMode"] = compact_mode return data /datum/component/uplink/ui_static_data(mob/user) diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm index 1adcb6fde6..d66445b8a1 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -9,7 +9,7 @@ /datum/spawners_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "spawners_menu", "Spawners Menu", 700, 600, master_ui, state) + ui = new(user, src, ui_key, "SpawnersMenu", "Spawners Menu", 700, 600, master_ui, state) ui.open() /datum/spawners_menu/ui_data(mob/user) diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index ad24126e45..be87503981 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -214,8 +214,8 @@ /datum/wires/ui_interact(mob/user, ui_key = "wires", datum/tgui/ui = null, force_open = FALSE, \ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "wires", "[holder.name] Wires", 350, 150 + wires.len * 30, master_ui, state) + if (!ui) + ui = new(user, src, ui_key, "Wires", "[holder.name] Wires", 350, 150 + wires.len * 30, master_ui, state) ui.open() /datum/wires/ui_data(mob/user) diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index 42904383bb..6c6b550cc5 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -202,7 +202,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "sleeper", name, 550, 700, master_ui, state) + ui = new(user, src, ui_key, "Sleeper", name, 550, 700, master_ui, state) ui.open() /obj/machinery/sleeper/ui_data() diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index 7eef48ebdd..20e703333d 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -105,12 +105,10 @@ GLOBAL_LIST_EMPTY(announcement_systems) /obj/machinery/announcement_system/ui_interact(mob/user) . = ..() - if(!user.canUseTopic(src, !hasSiliconAccessInArea(user))) - return - if(stat & BROKEN) - visible_message("[src] buzzes.", "You hear a faint buzz.") - playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 1) - return + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "AutomatedAnnouncement", "Automated Announcement System", 500, 225, master_ui, state) + ui.open() var/contents = "Arrival Announcement: ([(arrivalToggle ? "On" : "Off")])
\n[arrival]

\n" diff --git a/code/game/machinery/bank_machine.dm b/code/game/machinery/bank_machine.dm index 41867a8520..fabf8e3343 100644 --- a/code/game/machinery/bank_machine.dm +++ b/code/game/machinery/bank_machine.dm @@ -56,7 +56,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "bank_machine", name, 320, 165, master_ui, state) + ui = new(user, src, ui_key, "BankMachine", name, 320, 165, master_ui, state) ui.open() /obj/machinery/computer/bank_machine/ui_data(mob/user) diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index 7439fd1b8b..c8efe319db 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -46,7 +46,7 @@ /obj/machinery/computer/operating/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "operating_computer", name, 350, 470, master_ui, state) + ui = new(user, src, ui_key, "OperatingComputer", name, 350, 470, master_ui, state) ui.open() /obj/machinery/computer/operating/ui_data(mob/user) @@ -108,6 +108,49 @@ )) else data["patient"] = null + return data + switch(patient.stat) + if(CONSCIOUS) + data["patient"]["stat"] = "Conscious" + data["patient"]["statstate"] = "good" + if(SOFT_CRIT) + data["patient"]["stat"] = "Conscious" + data["patient"]["statstate"] = "average" + if(UNCONSCIOUS) + data["patient"]["stat"] = "Unconscious" + data["patient"]["statstate"] = "average" + if(DEAD) + data["patient"]["stat"] = "Dead" + data["patient"]["statstate"] = "bad" + data["patient"]["health"] = patient.health + data["patient"]["blood_type"] = patient.dna.blood_type + data["patient"]["maxHealth"] = patient.maxHealth + data["patient"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["patient"]["bruteLoss"] = patient.getBruteLoss() + data["patient"]["fireLoss"] = patient.getFireLoss() + data["patient"]["toxLoss"] = patient.getToxLoss() + data["patient"]["oxyLoss"] = patient.getOxyLoss() + data["procedures"] = list() + if(patient.surgeries.len) + for(var/datum/surgery/procedure in patient.surgeries) + var/datum/surgery_step/surgery_step = procedure.get_surgery_step() + var/chems_needed = surgery_step.get_chem_list() + var/alternative_step + var/alt_chems_needed = "" + if(surgery_step.repeatable) + var/datum/surgery_step/next_step = procedure.get_surgery_next_step() + if(next_step) + alternative_step = capitalize(next_step.name) + alt_chems_needed = next_step.get_chem_list() + else + alternative_step = "Finish operation" + data["procedures"] += list(list( + "name" = capitalize("[parse_zone(procedure.location)] [procedure.name]"), + "next_step" = capitalize(surgery_step.name), + "chems_needed" = chems_needed, + "alternative_step" = alternative_step, + "alt_chems_needed" = alt_chems_needed + )) return data /obj/machinery/computer/operating/ui_act(action, params) diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm index 9ff46c021b..d8553854b2 100644 --- a/code/game/machinery/computer/aifixer.dm +++ b/code/game/machinery/computer/aifixer.dm @@ -8,6 +8,8 @@ icon_keyboard = "tech_key" icon_screen = "ai-fixer" light_color = LIGHT_COLOR_PINK + ui_x = 370 + ui_y = 360 /obj/machinery/computer/aifixer/attackby(obj/I, mob/user, params) if(occupier && istype(I, /obj/item/screwdriver)) @@ -18,8 +20,12 @@ else return ..() -/obj/machinery/computer/aifixer/ui_interact(mob/user) - . = ..() +/obj/machinery/computer/aifixer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "AiRestorer", name, ui_x, ui_y, master_ui, state) + ui.open() var/dat = "" diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index 6df7120dcc..fbedf62734 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -23,7 +23,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_alert", name, 350, 300, master_ui, state) + ui = new(user, src, ui_key, "AtmosAlertConsole", name, 350, 300, master_ui, state) ui.open() /obj/machinery/computer/atmos_alert/ui_data(mob/user) diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index 22e102b54b..b891941056 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -127,7 +127,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_control", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/atmos_control/ui_data(mob/user) @@ -269,7 +269,7 @@ GLOBAL_LIST_EMPTY(atmos_air_controllers) datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_control", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "AtmosControlConsole", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/atmos_control/tank/ui_data(mob/user) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index eb7f194229..9c1177cb90 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -4,109 +4,166 @@ icon_screen = "cameras" icon_keyboard = "security_key" circuit = /obj/item/circuitboard/computer/security - var/last_pic = 1 - var/list/network = list("ss13") - var/list/watchers = list() //who's using the console, associated with the camera they're on. - light_color = LIGHT_COLOR_RED + ui_x = 870 + ui_y = 708 + + var/list/network = list("ss13") + var/obj/machinery/camera/active_camera + var/list/concurrent_users = list() + + // Stuff needed to render the map + var/map_name + var/const/default_map_size = 15 + var/obj/screen/cam_screen + var/obj/screen/plane_master/lighting/cam_plane_master + var/obj/screen/background/cam_background /obj/machinery/computer/security/Initialize() . = ..() + // Map name has to start and end with an A-Z character, + // and definitely NOT with a square bracket or even a number. + // I wasted 6 hours on this. :agony: + map_name = "camera_console_[REF(src)]_map" + // Convert networks to lowercase for(var/i in network) network -= i network += lowertext(i) - -/obj/machinery/computer/security/check_eye(mob/user) - if(!can_interact(user) || !(user in watchers) || !watchers[user]) - user.unset_machine() - return - var/obj/machinery/camera/C = watchers[user] - if(!C.can_use()) - user.unset_machine() - return - -/obj/machinery/computer/security/on_unset_machine(mob/user) - watchers.Remove(user) - user.reset_perspective(null) + // Initialize map objects + cam_screen = new + cam_screen.name = "screen" + cam_screen.assigned_map = map_name + cam_screen.del_on_map_removal = FALSE + cam_screen.screen_loc = "[map_name]:1,1" + cam_plane_master = new + cam_plane_master.name = "plane_master" + cam_plane_master.assigned_map = map_name + cam_plane_master.del_on_map_removal = FALSE + cam_plane_master.screen_loc = "[map_name]:CENTER" + cam_background = new + cam_background.assigned_map = map_name + cam_background.del_on_map_removal = FALSE /obj/machinery/computer/security/Destroy() - if(watchers.len) - for(var/mob/M in watchers) - M.unset_machine() //to properly reset the view of the users if the console is deleted. + qdel(cam_screen) + qdel(cam_plane_master) + qdel(cam_background) return ..() -/obj/machinery/computer/security/can_interact(mob/user) - if((!hasSiliconAccessInArea(user) && !Adjacent(user)) || is_blind(user) || !in_view_range(user, src)) - return FALSE - return ..() +/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + for(var/i in network) + network -= i + network += "[idnum][i]" -/obj/machinery/computer/security/interact(mob/user, special_state) +/obj/machinery/computer/security/ui_interact(\ + mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + // Update UI + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + if(!ui) + var/user_ref = REF(user) + var/is_living = isliving(user) + // Ghosts shouldn't count towards concurrent users, which produces + // an audible terminal_on click. + if(is_living) + concurrent_users += user_ref + // Turn on the console + if(length(concurrent_users) == 1 && is_living) + playsound(src, 'sound/machines/terminal_on.ogg', 25, FALSE) + use_power(active_power_usage) + // Register map objects + user.client.register_map_obj(cam_screen) + user.client.register_map_obj(cam_plane_master) + user.client.register_map_obj(cam_background) + // Open UI + ui = new(user, src, ui_key, "CameraConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/security/ui_data() + var/list/data = list() + data["network"] = network + data["activeCamera"] = null + if(active_camera) + data["activeCamera"] = list( + name = active_camera.c_tag, + status = active_camera.status, + ) + return data + +/obj/machinery/computer/security/ui_static_data() + var/list/data = list() + data["mapRef"] = map_name + var/list/cameras = get_available_cameras() + data["cameras"] = list() + for(var/i in cameras) + var/obj/machinery/camera/C = cameras[i] + data["cameras"] += list(list( + name = C.c_tag, + )) + return data + +/obj/machinery/computer/security/ui_act(action, params) . = ..() - if (ismob(user) && !isliving(user)) // ghosts don't need cameras - return - if (!network) - stack_trace("No camera network") - user.unset_machine() - return FALSE - if (!(islist(network))) - stack_trace("Camera network is not a list") - user.unset_machine() - return FALSE - - var/list/camera_list = get_available_cameras() - if(!(user in watchers)) - for(var/Num in camera_list) - var/obj/machinery/camera/CAM = camera_list[Num] - if(istype(CAM) && CAM.can_use()) - watchers[user] = CAM //let's give the user the first usable camera, and then let him change to the camera he wants. - break - if(!(user in watchers)) - user.unset_machine() // no usable camera on the network, we disconnect the user from the computer. - return FALSE - playsound(src, 'sound/machines/terminal_prompt.ogg', 25, 0) - use_camera_console(user) - -/obj/machinery/computer/security/proc/use_camera_console(mob/user) - var/list/camera_list = get_available_cameras() - var/t = input(user, "Which camera should you change to?") as null|anything in camera_list - if(!src || user.machine != src) //while we were choosing we got disconnected from our computer or are using another machine. - return - if(!t || t == "Cancel") - user.unset_machine() - playsound(src, 'sound/machines/terminal_off.ogg', 25, 0) + if(.) return - var/obj/machinery/camera/C = camera_list[t] + if(action == "switch_camera") + var/c_tag = params["name"] + var/list/cameras = get_available_cameras() + var/obj/machinery/camera/C = cameras[c_tag] + active_camera = C + playsound(src, get_sfx("terminal_type"), 25, FALSE) - if(!C || !C.can_use() || !can_interact(user)) - user.unset_machine() - return FALSE + // Show static if can't use the camera + if(!active_camera?.can_use()) + show_camera_static() + return TRUE - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0) - if(isAI(user)) - var/mob/living/silicon/ai/A = user - A.eyeobj.setLoc(get_turf(C)) - A.client.eye = A.eyeobj - else - user.reset_perspective(C) - user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash/static) - user.clear_fullscreen("flash", 5) - watchers[user] = C - use_power(50) - addtimer(CALLBACK(src, .proc/use_camera_console, user), 5) + var/list/visible_turfs = list() + for(var/turf/T in (C.isXRay() \ + ? range(C.view_range, C) \ + : view(C.view_range, C))) + visible_turfs += T -//returns the list of cameras accessible from this computer + var/list/bbox = get_bbox_of_atoms(visible_turfs) + var/size_x = bbox[3] - bbox[1] + 1 + var/size_y = bbox[4] - bbox[2] + 1 + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) + + return TRUE + +/obj/machinery/computer/security/ui_close(mob/user) + var/user_ref = REF(user) + var/is_living = isliving(user) + // Living creature or not, we remove you anyway. + concurrent_users -= user_ref + // Unregister map objects + user.client.clear_map(map_name) + // Turn off the console + if(length(concurrent_users) == 0 && is_living) + active_camera = null + playsound(src, 'sound/machines/terminal_off.ogg', 25, FALSE) + use_power(0) + +/obj/machinery/computer/security/proc/show_camera_static() + cam_screen.vis_contents.Cut() + cam_background.icon_state = "scanline2" + cam_background.fill_rect(1, 1, default_map_size, default_map_size) + +// Returns the list of cameras accessible from this computer /obj/machinery/computer/security/proc/get_available_cameras() var/list/L = list() for (var/obj/machinery/camera/C in GLOB.cameranet.cameras) if((is_away_level(z) || is_away_level(C.z)) && (C.z != z))//if on away mission, can only receive feed from same z_level cameras continue L.Add(C) - - camera_sort(L) - var/list/D = list() - D["Cancel"] = "Cancel" for(var/obj/machinery/camera/C in L) if(!C.network) stack_trace("Camera in a cameranet has no camera network") @@ -114,9 +171,9 @@ if(!(islist(C.network))) stack_trace("Camera in a cameranet has a non-list camera network") continue - var/list/tempnetwork = C.network&network + var/list/tempnetwork = C.network & network if(tempnetwork.len) - D["[C.c_tag][(C.status ? null : " (Deactivated)")]"] = C + D["[C.c_tag]"] = C return D // SECURITY MONITORS @@ -127,7 +184,6 @@ icon_state = "television" icon_keyboard = null icon_screen = "detective_tv" - clockwork = TRUE //it'd look weird pass_flags = PASSTABLE /obj/machinery/computer/security/mining @@ -145,7 +201,7 @@ circuit = /obj/item/circuitboard/computer/research /obj/machinery/computer/security/hos - name = "Head of Security's camera console" + name = "\improper Head of Security's camera console" desc = "A custom security console with added access to the labor camp network." network = list("ss13", "labor") circuit = null @@ -157,7 +213,7 @@ circuit = null /obj/machinery/computer/security/qm - name = "Quartermaster's camera console" + name = "\improper Quartermaster's camera console" desc = "A console with access to the mining, auxillary base and vault camera networks." network = list("mine", "auxbase", "vault") circuit = null @@ -169,10 +225,10 @@ desc = "Used for watching an empty arena." icon = 'icons/obj/stationobjs.dmi' icon_state = "telescreen" + layer = SIGN_LAYER network = list("thunder") density = FALSE circuit = null - clockwork = TRUE //it'd look very weird light_power = 0 /obj/machinery/computer/security/telescreen/update_icon_state() @@ -184,38 +240,62 @@ name = "entertainment monitor" desc = "Damn, they better have the /tg/ channel on these things." icon = 'icons/obj/status_display.dmi' - icon_state = "entertainment" + icon_state = "entertainment_blank" network = list("thunder") + density = FALSE + circuit = null + interaction_flags_atom = NONE // interact() is called by BigClick() + var/icon_state_off = "entertainment_blank" + var/icon_state_on = "entertainment" + +/obj/machinery/computer/security/telescreen/entertainment/Initialize() + . = ..() + RegisterSignal(src, COMSIG_CLICK, .proc/BigClick) + +// Bypass clickchain to allow humans to use the telescreen from a distance +/obj/machinery/computer/security/telescreen/entertainment/proc/BigClick() + interact(usr) + +/obj/machinery/computer/security/telescreen/entertainment/proc/notify(on) + if(on && icon_state == icon_state_off) + say(pick( + "Feats of bravery live now at the thunderdome!", + "Two enter, one leaves! Tune in now!", + "Violence like you've never seen it before!", + "Spears! Camera! Action! LIVE NOW!")) + icon_state = icon_state_on + else + icon_state = icon_state_off /obj/machinery/computer/security/telescreen/rd - name = "Research Director's telescreen" + name = "\improper Research Director's telescreen" desc = "Used for watching the AI and the RD's goons from the safety of his office." network = list("rd", "aicore", "aiupload", "minisat", "xeno", "test") -/obj/machinery/computer/security/telescreen/circuitry - name = "circuitry telescreen" - desc = "Used for watching the other eggheads from the safety of the circuitry lab." +/obj/machinery/computer/security/telescreen/research + name = "research telescreen" + desc = "A telescreen with access to the research division's camera network." network = list("rd") /obj/machinery/computer/security/telescreen/ce - name = "Chief Engineer's telescreen" + name = "\improper Chief Engineer's telescreen" desc = "Used for watching the engine, telecommunications and the minisat." network = list("engine", "singularity", "tcomms", "minisat") /obj/machinery/computer/security/telescreen/cmo - name = "Chief Medical Officer's telescreen" + name = "\improper Chief Medical Officer's telescreen" desc = "A telescreen with access to the medbay's camera network." network = list("medbay") /obj/machinery/computer/security/telescreen/vault - name = "Vault monitor" + name = "vault monitor" desc = "A telescreen that connects to the vault's camera network." network = list("vault") /obj/machinery/computer/security/telescreen/toxins - name = "Bomb test site monitor" + name = "bomb test site monitor" desc = "A telescreen that connects to the bomb test site's camera." - network = list("toxin") + network = list("toxins") /obj/machinery/computer/security/telescreen/engine name = "engine monitor" @@ -248,6 +328,6 @@ network = list("minisat") /obj/machinery/computer/security/telescreen/aiupload - name = "AI upload monitor" + name = "\improper AI upload monitor" desc = "A telescreen that connects to the AI upload's camera network." network = list("aiupload") diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 01a1d043a2..cd8b5c1c59 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -80,7 +80,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new) datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if (!ui) - ui = new(user, src, ui_key, "crew", "crew monitor", 800, 600 , master_ui, state) + ui = new(user, src, ui_key, "CrewConsole", "crew monitor", 800, 600 , master_ui, state) ui.open() /datum/crewmonitor/proc/show(mob/M, source) diff --git a/code/game/machinery/computer/dna_console.dm b/code/game/machinery/computer/dna_console.dm index b9bd3da95e..38bc5198db 100644 --- a/code/game/machinery/computer/dna_console.dm +++ b/code/game/machinery/computer/dna_console.dm @@ -441,19 +441,9 @@ var/location = viable_occupant.dna.mutation_index.Find(mutation) //We do this because we dont want people using sysexp or similair tools to just read the mutations. - if(!location) //Do this only when needed, dont make a list with mutations for every iteration if you dont need to - var/list/mutations = get_mutation_list(TRUE) - if(mutation in mutations) - location = mutations.Find(mutation) - if(mutation == current_mutation) - class = "selected" - if(location > DNA_MUTATION_BLOCKS) - temp_html += "Extra Mutation" - else if(mutation in stored_research.discovered_mutations) - temp_html += "Discovered Mutation" - else - temp_html += "Undiscovered" - return temp_html + if(!ui) + ui = new(user, src, ui_key, "DnaConsole", name, 539, 710, master_ui, state) + ui.open() /obj/machinery/computer/scan_consolenew/proc/display_sequence(mutation, storage_slot) //Storage slot is for when viewing from the stored mutations var/temp_html = "" diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm index 1924cd9f23..fdbe2f1060 100644 --- a/code/game/machinery/computer/launchpad_control.dm +++ b/code/game/machinery/computer/launchpad_control.dm @@ -56,7 +56,7 @@ /obj/machinery/computer/launchpad/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "launchpad_console", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "LaunchpadConsole", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/launchpad/ui_data(mob/user) diff --git a/code/game/machinery/computer/prisoner/gulag_teleporter.dm b/code/game/machinery/computer/prisoner/gulag_teleporter.dm index e4a6a0b2d4..9055ff62ec 100644 --- a/code/game/machinery/computer/prisoner/gulag_teleporter.dm +++ b/code/game/machinery/computer/prisoner/gulag_teleporter.dm @@ -25,7 +25,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "gulag_console", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "GulagTeleporterConsole", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/prisoner/gulag_teleporter_computer/ui_data(mob/user) diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index 2621616759..0f03c9405e 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -8,6 +8,8 @@ var/temp = null light_color = LIGHT_COLOR_PINK + ui_x = 500 + ui_y = 460 /obj/machinery/computer/robotics/proc/can_control(mob/user, mob/living/silicon/robot/R) if(!istype(R)) @@ -25,14 +27,25 @@ return FALSE return TRUE -/obj/machinery/computer/robotics/ui_interact(mob/user) - . = ..() - if (src.z > 6) - to_chat(user, "Unable to establish a connection: \black You're too far away from the station!") - return - user.set_machine(src) - var/dat - var/robots = 0 +/obj/machinery/computer/robotics/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "RoboticsControlConsole", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/robotics/ui_data(mob/user) + var/list/data = list() + + data["can_hack"] = FALSE + if(issilicon(user)) + var/mob/living/silicon/S = user + if(S.hack_software) + data["can_hack"] = TRUE + else if(IsAdminGhost(user)) + data["can_hack"] = TRUE + + data["cyborgs"] = list() for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) if(!can_control(user, R)) continue diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index fcb18b4dff..b4340b9350 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -20,7 +20,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "station_alert", name, 325, 500, master_ui, state) + ui = new(user, src, ui_key, "StationAlertConsole", name, 325, 500, master_ui, state) ui.open() /obj/machinery/computer/station_alert/ui_data(mob/user) diff --git a/code/game/machinery/computer/teleporter.dm b/code/game/machinery/computer/teleporter.dm index 6710258626..e7957a5b75 100644 --- a/code/game/machinery/computer/teleporter.dm +++ b/code/game/machinery/computer/teleporter.dm @@ -38,7 +38,7 @@ obj/machinery/computer/teleporter/ui_interact(mob/user, ui_key = "main", datum/t datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "teleporter", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "Teleporter", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/teleporter/ui_data(mob/user) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 3bc8aff809..929de49293 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1434,7 +1434,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "ai_airlock", name, 500, 390, master_ui, state) + ui = new(user, src, ui_key, "AiAirlock", name, 500, 390, master_ui, state) ui.open() return TRUE diff --git a/code/game/machinery/doors/airlock_electronics.dm b/code/game/machinery/doors/airlock_electronics.dm index 7b90715f2b..32ebdba7a7 100644 --- a/code/game/machinery/doors/airlock_electronics.dm +++ b/code/game/machinery/doors/airlock_electronics.dm @@ -14,7 +14,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "airlock_electronics", name, 420, 485, master_ui, state) + ui = new(user, src, ui_key, "AirlockElectronics", name, 420, 485, master_ui, state) ui.open() /obj/item/electronics/airlock/ui_data() diff --git a/code/game/machinery/doors/brigdoors.dm b/code/game/machinery/doors/brigdoors.dm index adc1748ded..05f4c03eb0 100644 --- a/code/game/machinery/doors/brigdoors.dm +++ b/code/game/machinery/doors/brigdoors.dm @@ -143,7 +143,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "brig_timer", name, 300, 138, master_ui, state) + ui = new(user, src, ui_key, "BrigTimer", name, 300, 138, master_ui, state) ui.open() //icon update function diff --git a/code/game/machinery/gulag_item_reclaimer.dm b/code/game/machinery/gulag_item_reclaimer.dm index 55b1e34022..45484caa77 100644 --- a/code/game/machinery/gulag_item_reclaimer.dm +++ b/code/game/machinery/gulag_item_reclaimer.dm @@ -31,7 +31,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "gulag_item_reclaimer", name, 300, 400, master_ui, state) + ui = new(user, src, ui_key, "GulagItemReclaimer", name, 300, 400, master_ui, state) ui.open() /obj/machinery/gulag_item_reclaimer/ui_data(mob/user) diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm index d01f7e3e40..d3247253c6 100644 --- a/code/game/machinery/launch_pad.dm +++ b/code/game/machinery/launch_pad.dm @@ -285,8 +285,7 @@ /obj/item/launchpad_remote/ui_interact(mob/user, ui_key = "launchpad_remote", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "launchpad_remote", "Briefcase Launchpad Remote", 300, 240, master_ui, state) //width, height - ui.set_style("syndicate") + ui = new(user, src, ui_key, "LaunchpadRemote", "Briefcase Launchpad Remote", 300, 240, master_ui, state) //width, height ui.open() ui.set_autoupdate(TRUE) diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm index 41dfe8e3ac..af8f1af9f9 100644 --- a/code/game/machinery/spaceheater.dm +++ b/code/game/machinery/spaceheater.dm @@ -170,7 +170,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "space_heater", name, 400, 305, master_ui, state) + ui = new(user, src, ui_key, "SpaceHeater", name, 400, 305, master_ui, state) ui.open() /obj/machinery/space_heater/ui_data() diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index cf6b2b4bf4..7de414fd63 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -383,7 +383,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "suit_storage_unit", name, 400, 305, master_ui, state) + ui = new(user, src, ui_key, "SuitStorageUnit", name, 400, 305, master_ui, state) ui.open() /obj/machinery/suit_storage_unit/ui_data() diff --git a/code/game/mecha/mech_bay.dm b/code/game/mecha/mech_bay.dm index bcf1b948e2..dcd8a12b38 100644 --- a/code/game/mecha/mech_bay.dm +++ b/code/game/mecha/mech_bay.dm @@ -84,7 +84,7 @@ /obj/machinery/computer/mech_bay_power_console/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "mech_bay_power_console", "Mech Bay Power Control Console", 400, 200, master_ui, state) + ui = new(user, src, ui_key, "MechBayPowerConsole", "Mech Bay Power Control Console", 400, 200, master_ui, state) ui.open() /obj/machinery/computer/mech_bay_power_console/ui_act(action, params) diff --git a/code/game/mecha/mecha_control_console.dm b/code/game/mecha/mecha_control_console.dm index 91c97ff14a..e492f01709 100644 --- a/code/game/mecha/mecha_control_console.dm +++ b/code/game/mecha/mecha_control_console.dm @@ -8,22 +8,15 @@ var/list/located = list() var/screen = 0 var/stored_data - -/obj/machinery/computer/mecha/ui_interact(mob/user) - . = ..() - var/dat = "[src.name]" - if(screen == 0) - dat += "

Tracking beacons data

" - var/list/trackerlist = list() - for(var/obj/mecha/MC in GLOB.mechas_list) - trackerlist += MC.trackers - for(var/obj/item/mecha_parts/mecha_tracking/TR in trackerlist) - var/answer = TR.get_mecha_info() - if(answer) - dat += {"
[answer]
- Send message
- Show exosuit log
- [TR.recharging?"Recharging EMP Pulse...
":"(EMP Pulse)
"]"} + ui_x = 500 + ui_y = 500 + +/obj/machinery/computer/mecha/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ExosuitControlConsole", name, ui_x, ui_y, master_ui, state) + ui.open() if(screen==1) dat += "

Log contents

" diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm index 665082d1d2..93d73b2b5f 100644 --- a/code/game/objects/items/RPD.dm +++ b/code/game/objects/items/RPD.dm @@ -249,7 +249,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list( var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/pipes) assets.send(user) - ui = new(user, src, ui_key, "rpd", name, 425, 472, master_ui, state) + ui = new(user, src, ui_key, "RapidPipeDispenser", name, 425, 515, master_ui, state) ui.open() /obj/item/pipe_dispenser/ui_data(mob/user) diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 09c045497c..5012170b4b 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -147,7 +147,7 @@ SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "crayon", name, 600, 600, + ui = new(user, src, ui_key, "Crayon", name, 600, 600, master_ui, state) ui.open() diff --git a/code/game/objects/items/devices/aicard.dm b/code/game/objects/items/devices/aicard.dm index 38b117b132..bfb9348d97 100644 --- a/code/game/objects/items/devices/aicard.dm +++ b/code/game/objects/items/devices/aicard.dm @@ -58,7 +58,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "intellicard", name, 500, 500, master_ui, state) + ui = new(user, src, ui_key, "Intellicard", name, 500, 500, master_ui, state) ui.open() /obj/item/aicard/ui_data() diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index 1eee083c80..c364d1eb48 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -16,6 +16,9 @@ var/on = TRUE var/shock_cooldown = FALSE + var/ui_x = 260 + var/ui_y = 137 + /obj/item/electropack/suicide_act(mob/living/carbon/user) user.visible_message("[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!") return (FIRELOSS) @@ -128,22 +131,12 @@ if(!ishuman(user)) return - user.set_machine(src) - var/dat = {" - -Turned [on ? "On" : "Off"] - Toggle
-Frequency/Code for electropack:
-Frequency: -[format_frequency(src.frequency)] -Set
- -Code: -[src.code] -Set
-
"} - user << browse(dat, "window=radio") - onclose(user, "radio") - return +/obj/item/electropack/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Electropack", name, ui_x, ui_y, master_ui, state) + ui.open() /obj/item/electropack/shockcollar name = "shock collar" diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 60b93d9461..ae705168a9 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -119,7 +119,7 @@ ui_height += 6 + channels.len * 21 else ui_height += 24 - ui = new(user, src, ui_key, "radio", name, ui_width, ui_height, master_ui, state) + ui = new(user, src, ui_key, "Radio", name, ui_width, ui_height, master_ui, state) ui.open() /obj/item/radio/ui_data(mob/user) diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index 4bab5a5bcd..f48b9e2a0f 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -76,6 +76,9 @@ effective or pretty fucking useless. var/used = 0 // is it cooling down? var/stealth = FALSE + var/ui_x = 320 + var/ui_y = 335 + /obj/item/healthanalyzer/rad_laser/attack(mob/living/M, mob/living/user) if(!stealth || !irradiate) ..() @@ -111,7 +114,12 @@ effective or pretty fucking useless. ui_interact(user) /obj/item/healthanalyzer/rad_laser/ui_interact(mob/user) - . = ..() +/obj/item/healthanalyzer/rad_laser/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "RadioactiveMicrolaser", "Radioactive Microlaser", ui_x, ui_y, master_ui, state) + ui.open() var/dat = "Irradiation: [irradiate ? "On" : "Off"]
" dat += "Stealth Mode (NOTE: Deactivates automatically while Irradiation is off): [stealth ? "On" : "Off"]
" diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index b929642f33..6124138aeb 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -13,6 +13,8 @@ var/mob/attacher = null var/valve_open = FALSE var/toggle = 1 + var/ui_x = 310 + var/ui_y = 320 /obj/item/transfer_valve/IsAssemblyHolder() return TRUE @@ -235,3 +237,52 @@ // eventually maybe have it update icon to show state (timer, prox etc.) like old bombs /obj/item/transfer_valve/proc/c_state() return + +/obj/item/transfer_valve/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "TransferValve", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/transfer_valve/ui_data(mob/user) + var/list/data = list() + data["tank_one"] = tank_one + data["tank_two"] = tank_two + data["attached_device"] = attached_device + data["valve"] = valve_open + return data + +/obj/item/transfer_valve/ui_act(action, params) + if(..()) + return + + switch(action) + if("tankone") + if(tank_one) + split_gases() + valve_open = FALSE + tank_one.forceMove(drop_location()) + tank_one = null + . = TRUE + if("tanktwo") + if(tank_two) + split_gases() + valve_open = FALSE + tank_two.forceMove(drop_location()) + tank_two = null + . = TRUE + if("toggle") + toggle_valve() + . = TRUE + if("device") + if(attached_device) + attached_device.attack_self(usr) + . = TRUE + if("remove_device") + if(attached_device) + attached_device.on_detach() + attached_device = null + . = TRUE + + update_icon() diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm index c4a15a1871..837f57ceb5 100644 --- a/code/game/objects/items/eightball.dm +++ b/code/game/objects/items/eightball.dm @@ -196,7 +196,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "eightball", name, 400, 600, master_ui, state) + ui = new(user, src, ui_key, "EightBallVote", name, 400, 600, master_ui, state) ui.open() /obj/item/toy/eightball/haunted/ui_data(mob/user) diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 58e16ad615..c76977947f 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -159,7 +159,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "tanks", name, 400, 120, master_ui, state) + ui = new(user, src, ui_key, "Tank", name, 400, 120, master_ui, state) ui.open() /obj/item/tank/ui_data(mob/user) diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm index 405e697d3b..627a41349e 100644 --- a/code/game/objects/structures/artstuff.dm +++ b/code/game/objects/structures/artstuff.dm @@ -35,101 +35,368 @@ else painting = null - -////////////// -// CANVASES // -////////////// - -#define AMT_OF_CANVASES 4 //Keep this up to date or shit will break. - -//To safe memory on making /icons we cache the blanks.. -GLOBAL_LIST_INIT(globalBlankCanvases, new(AMT_OF_CANVASES)) - /obj/item/canvas name = "canvas" desc = "Draw out your soul on this canvas!" icon = 'icons/obj/artstuff.dmi' icon_state = "11x11" resistance_flags = FLAMMABLE - var/whichGlobalBackup = 1 //List index + var/width = 11 + var/height = 11 + var/list/grid + var/canvas_color = "#ffffff" //empty canvas color + var/ui_x = 400 + var/ui_y = 400 + var/used = FALSE + var/painting_name //Painting name, this is set after framing. + var/finalized = FALSE //Blocks edits + var/author_ckey + var/icon_generated = FALSE + var/icon/generated_icon -/obj/item/canvas/nineteenXnineteen - icon_state = "19x19" - whichGlobalBackup = 2 + // Painting overlay offset when framed + var/framed_offset_x = 11 + var/framed_offset_y = 10 -/obj/item/canvas/twentythreeXnineteen - icon_state = "23x19" - whichGlobalBackup = 3 + pixel_x = 10 + pixel_y = 9 -/obj/item/canvas/twentythreeXtwentythree - icon_state = "23x23" - whichGlobalBackup = 4 - -//HEY YOU -//ARE YOU READING THE CODE FOR CANVASES? -//ARE YOU AWARE THEY CRASH HALF THE SERVER WHEN SOMEONE DRAWS ON THEM... -//...AND NOBODY CAN FIGURE OUT WHY? -//THEN GO ON BRAVE TRAVELER -//TRY TO FIX THEM AND REMOVE THIS CODE /obj/item/canvas/Initialize() - ..() - return INITIALIZE_HINT_QDEL //Delete on creation + . = ..() + reset_grid() -//Find the right size blank canvas -/obj/item/canvas/proc/getGlobalBackup() - . = null - if(GLOB.globalBlankCanvases[whichGlobalBackup]) - . = GLOB.globalBlankCanvases[whichGlobalBackup] - else - var/icon/I = icon(initial(icon),initial(icon_state)) - GLOB.globalBlankCanvases[whichGlobalBackup] = I - . = I +/obj/item/canvas/proc/reset_grid() + grid = new/list(width,height) + for(var/x in 1 to width) + for(var/y in 1 to height) + grid[x][y] = canvas_color +/obj/item/canvas/attack_self(mob/user) + . = ..() + ui_interact(user) +/obj/item/canvas/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) -//One pixel increments -/obj/item/canvas/attackby(obj/item/I, mob/user, params) - //Click info - var/list/click_params = params2list(params) - var/pixX = text2num(click_params["icon-x"]) - var/pixY = text2num(click_params["icon-y"]) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Canvas", name, ui_x, ui_y, master_ui, state) + ui.set_autoupdate(FALSE) + ui.open() - //Should always be true, otherwise you didn't click the object, but let's check because SS13~ - if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) - return - - //Cleaning one pixel with a soap or rag - if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/rag)) - //Pixel info created only when needed - var/icon/masterpiece = icon(icon,icon_state) - var/thePix = masterpiece.GetPixel(pixX,pixY) - var/icon/Ico = getGlobalBackup() - if(!Ico) - qdel(masterpiece) - return - - var/theOriginalPix = Ico.GetPixel(pixX,pixY) - if(thePix != theOriginalPix) //colour changed - DrawPixelOn(theOriginalPix,pixX,pixY) - qdel(masterpiece) - - //Drawing one pixel with a crayon - else if(istype(I, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/C = I - DrawPixelOn(C.paint_color, pixX, pixY) +/obj/item/canvas/attackby(obj/item/I, mob/living/user, params) + if(user.a_intent == INTENT_HELP) + ui_interact(user) else return ..() +/obj/item/canvas/ui_data(mob/user) + . = ..() + .["grid"] = grid + .["name"] = painting_name + .["finalized"] = finalized -//Clean the whole canvas -/obj/item/canvas/attack_self(mob/user) - if(!user) +/obj/item/canvas/examine(mob/user) + . = ..() + ui_interact(user) + +/obj/item/canvas/ui_act(action, params) + . = ..() + if(. || finalized) return - var/icon/blank = getGlobalBackup() - if(blank) - //it's basically a giant etch-a-sketch - icon = blank - user.visible_message("[user] cleans the canvas.","You clean the canvas.") + var/mob/user = usr + switch(action) + if("paint") + var/obj/item/I = user.get_active_held_item() + var/color = get_paint_tool_color(I) + if(!color) + return FALSE + var/x = text2num(params["x"]) + var/y = text2num(params["y"]) + grid[x][y] = color + used = TRUE + update_icon() + . = TRUE + if("finalize") + . = TRUE + if(!finalized) + finalize(user) + +/obj/item/canvas/proc/finalize(mob/user) + finalized = TRUE + author_ckey = user.ckey + generate_proper_overlay() + try_rename(user) + +/obj/item/canvas/update_overlays() + . = ..() + if(!icon_generated) + if(used) + var/mutable_appearance/detail = mutable_appearance(icon,"[icon_state]wip") + detail.pixel_x = 1 + detail.pixel_y = 1 + . += detail + else + var/mutable_appearance/detail = mutable_appearance(generated_icon) + detail.pixel_x = 1 + detail.pixel_y = 1 + . += detail + +/obj/item/canvas/proc/generate_proper_overlay() + if(icon_generated) + return + var/png_filename = "data/paintings/temp_painting.png" + var/result = rustg_dmi_create_png(png_filename,"[width]","[height]",get_data_string()) + if(result) + CRASH("Error generating painting png : [result]") + generated_icon = new(png_filename) + icon_generated = TRUE + update_icon() + +/obj/item/canvas/proc/get_data_string() + var/list/data = list() + for(var/y in 1 to height) + for(var/x in 1 to width) + data += grid[x][y] + return data.Join("") + +//Todo make this element ? +/obj/item/canvas/proc/get_paint_tool_color(obj/item/I) + if(!I) + return + if(istype(I, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/C = I + return C.paint_color + else if(istype(I, /obj/item/pen)) + var/obj/item/pen/P = I + switch(P.colour) + if("black") + return "#000000" + if("blue") + return "#0000ff" + if("red") + return "#ff0000" + return P.colour + else if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/glass/rag)) + return canvas_color + +/obj/item/canvas/proc/try_rename(mob/user) + var/new_name = stripped_input(user,"What do you want to name the painting?") + if(!painting_name && new_name && user.canUseTopic(src,BE_CLOSE)) + painting_name = new_name + SStgui.update_uis(src) + +/obj/item/canvas/nineteenXnineteen + icon_state = "19x19" + width = 19 + height = 19 + ui_x = 600 + ui_y = 600 + pixel_x = 6 + pixel_y = 9 + framed_offset_x = 8 + framed_offset_y = 9 + +/obj/item/canvas/twentythreeXnineteen + icon_state = "23x19" + width = 23 + height = 19 + ui_x = 800 + ui_y = 600 + pixel_x = 4 + pixel_y = 10 + framed_offset_x = 6 + framed_offset_y = 8 + +/obj/item/canvas/twentythreeXtwentythree + icon_state = "23x23" + width = 23 + height = 23 + ui_x = 800 + ui_y = 800 + pixel_x = 5 + pixel_y = 9 + framed_offset_x = 5 + framed_offset_y = 6 + +/obj/item/wallframe/painting + name = "painting frame" + desc = "The perfect showcase for your favorite deathtrap memories." + icon = 'icons/obj/decals.dmi' + custom_materials = null + flags_1 = 0 + icon_state = "frame-empty" + result_path = /obj/structure/sign/painting + +/obj/structure/sign/painting + name = "Painting" + desc = "Art or \"Art\"? You decide." + icon = 'icons/obj/decals.dmi' + icon_state = "frame-empty" + buildable_sign = FALSE + var/obj/item/canvas/C + var/persistence_id + +/obj/structure/sign/painting/Initialize(mapload, dir, building) + . = ..() + SSpersistence.painting_frames += src + AddComponent(/datum/component/art, 20) + if(dir) + setDir(dir) + if(building) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + +/obj/structure/sign/painting/Destroy() + . = ..() + SSpersistence.painting_frames -= src + +/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params) + if(!C && istype(I, /obj/item/canvas)) + frame_canvas(user,I) + else if(C && !C.painting_name && istype(I,/obj/item/pen)) + try_rename(user) + else + return ..() + +/obj/structure/sign/painting/examine(mob/user) + . = ..() + if(C) + C.ui_interact(user,state = GLOB.physical_obscured_state) + +/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I) + . = ..() + if(C) + C.forceMove(drop_location()) + C = null + to_chat(user, "You remove the painting from the frame.") + update_icon() + return TRUE + +/obj/structure/sign/painting/proc/frame_canvas(mob/user,obj/item/canvas/new_canvas) + if(user.transferItemToLoc(new_canvas,src)) + C = new_canvas + if(!C.finalized) + C.finalize(user) + to_chat(user,"You frame [C].") + update_icon() + +/obj/structure/sign/painting/proc/try_rename(mob/user) + if(!C.painting_name) + C.try_rename(user) + +/obj/structure/sign/painting/update_icon_state() + . = ..() + if(C && C.generated_icon) + icon_state = null + else + icon_state = "frame-empty" -#undef AMT_OF_CANVASES +/obj/structure/sign/painting/update_overlays() + . = ..() + if(C && C.generated_icon) + var/mutable_appearance/MA = mutable_appearance(C.generated_icon) + MA.pixel_x = C.framed_offset_x + MA.pixel_y = C.framed_offset_y + . += MA + var/mutable_appearance/frame = mutable_appearance(C.icon,"[C.icon_state]frame") + frame.pixel_x = C.framed_offset_x - 1 + frame.pixel_y = C.framed_offset_y - 1 + . += frame + +/obj/structure/sign/painting/proc/load_persistent() + if(!persistence_id) + return + if(!SSpersistence.paintings || !SSpersistence.paintings[persistence_id] || !length(SSpersistence.paintings[persistence_id])) + return + var/list/chosen = pick(SSpersistence.paintings[persistence_id]) + var/title = chosen["title"] + var/author = chosen["ckey"] + var/png = "data/paintings/[persistence_id]/[chosen["md5"]].png" + if(!fexists(png)) + stack_trace("Persistent painting [chosen["md5"]].png was not found in [persistence_id] directory.") + return + var/icon/I = new(png) + var/obj/item/canvas/new_canvas + var/w = I.Width() + var/h = I.Height() + for(var/T in typesof(/obj/item/canvas)) + new_canvas = T + if(initial(new_canvas.width) == w && initial(new_canvas.height) == h) + new_canvas = new T(src) + break + new_canvas.fill_grid_from_icon(I) + new_canvas.generated_icon = I + new_canvas.icon_generated = TRUE + new_canvas.finalized = TRUE + new_canvas.painting_name = title + new_canvas.author_ckey = author + C = new_canvas + update_icon() + +/obj/structure/sign/painting/proc/save_persistent() + if(!persistence_id || !C) + return + if(sanitize_filename(persistence_id) != persistence_id) + stack_trace("Invalid persistence_id - [persistence_id]") + return + var/data = C.get_data_string() + var/md5 = md5(data) + var/list/current = SSpersistence.paintings[persistence_id] + if(!current) + current = list() + for(var/list/entry in current) + if(entry["md5"] == md5) + return + var/png_directory = "data/paintings/[persistence_id]/" + var/png_path = png_directory + "[md5].png" + var/result = rustg_dmi_create_png(png_path,"[C.width]","[C.height]",data) + if(result) + CRASH("Error saving persistent painting: [result]") + current += list(list("title" = C.painting_name , "md5" = md5, "ckey" = C.author_ckey)) + SSpersistence.paintings[persistence_id] = current + +/obj/item/canvas/proc/fill_grid_from_icon(icon/I) + var/h = I.Height() + 1 + for(var/x in 1 to width) + for(var/y in 1 to height) + grid[x][y] = I.GetPixel(x,h-y) + +//Presets for art gallery mapping, for paintings to be shared across stations +/obj/structure/sign/painting/library + persistence_id = "library" + +/obj/structure/sign/painting/library_secure + persistence_id = "library_secure" + +/obj/structure/sign/painting/library_private // keep your smut away from prying eyes, or non-librarians at least + persistence_id = "library_private" + +/obj/structure/sign/painting/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION(VV_HK_REMOVE_PAINTING, "Remove Persistent Painting") + +/obj/structure/sign/painting/vv_do_topic(list/href_list) + . = ..() + if(href_list[VV_HK_REMOVE_PAINTING]) + if(!check_rights(NONE)) + return + var/mob/user = usr + if(!persistence_id || !C) + to_chat(user,"This is not a persistent painting.") + return + var/md5 = md5(C.get_data_string()) + var/author = C.author_ckey + var/list/current = SSpersistence.paintings[persistence_id] + if(current) + for(var/list/entry in current) + if(entry["md5"] == md5) + current -= entry + var/png = "data/paintings/[persistence_id]/[md5].png" + fdel(png) + for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames) + if(P.C && md5(P.C.get_data_string()) == md5) + QDEL_NULL(P.C) + log_admin("[key_name(user)] has deleted a persistent painting made by [author].") + message_admins("[key_name_admin(user)] has deleted persistent painting made by [author].") diff --git a/code/game/objects/structures/tank_dispenser.dm b/code/game/objects/structures/tank_dispenser.dm index 6a8175b921..cf9cf2a830 100644 --- a/code/game/objects/structures/tank_dispenser.dm +++ b/code/game/objects/structures/tank_dispenser.dm @@ -71,7 +71,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "tank_dispenser", name, 275, 103, master_ui, state) + ui = new(user, src, ui_key, "TankDispenser", name, 275, 103, master_ui, state) ui.open() /obj/structure/tank_dispenser/ui_data(mob/user) diff --git a/code/modules/NTNet/relays.dm b/code/modules/NTNet/relays.dm index 0d855f15bb..2101a72960 100644 --- a/code/modules/NTNet/relays.dm +++ b/code/modules/NTNet/relays.dm @@ -69,7 +69,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "ntnet_relay", "NTNet Quantum Relay", ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "NtnetRelay", "NTNet Quantum Relay", ui_x, ui_y, master_ui, state) ui.open() diff --git a/code/modules/admin/verbs/borgpanel.dm b/code/modules/admin/verbs/borgpanel.dm index 83f2839438..beec54265e 100644 --- a/code/modules/admin/verbs/borgpanel.dm +++ b/code/modules/admin/verbs/borgpanel.dm @@ -36,7 +36,7 @@ /datum/borgpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "borgopanel", "Borg Panel", 700, 700, master_ui, state) + ui = new(user, src, ui_key, "BorgPanel", "Borg Panel", 700, 700, master_ui, state) ui.open() /datum/borgpanel/ui_data(mob/user) diff --git a/code/modules/antagonists/changeling/cellular_emporium.dm b/code/modules/antagonists/changeling/cellular_emporium.dm index b2c1a52a4a..6abefeefe7 100644 --- a/code/modules/antagonists/changeling/cellular_emporium.dm +++ b/code/modules/antagonists/changeling/cellular_emporium.dm @@ -16,7 +16,7 @@ /datum/cellular_emporium/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "cellular_emporium", name, 900, 480, master_ui, state) + ui = new(user, src, ui_key, "CellularEmporium", name, 900, 480, master_ui, state) ui.open() /datum/cellular_emporium/ui_data(mob/user) diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index b4f896fa08..ae8a23aaf5 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -7,6 +7,9 @@ density = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + ui_x = 350 + ui_y = 442 + var/timer_set = 90 var/default_timer_set = 90 var/minimum_timer_set = 90 @@ -262,8 +265,7 @@ /obj/machinery/nuclearbomb/ui_interact(mob/user, ui_key="main", datum/tgui/ui=null, force_open=0, datum/tgui/master_ui=null, datum/ui_state/state=GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "nuclear_bomb", name, 350, 442, master_ui, state) - ui.set_style(ui_style) + ui = new(user, src, ui_key, "NuclearBomb", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/nuclearbomb/ui_data(mob/user) diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm index cddc4fb08f..0af6c85fb6 100644 --- a/code/modules/assembly/health.dm +++ b/code/modules/assembly/health.dm @@ -4,7 +4,6 @@ icon_state = "health" custom_materials = list(/datum/material/iron=800, /datum/material/glass=200) attachable = TRUE - secured = FALSE var/scanning = FALSE var/health_scan @@ -12,7 +11,8 @@ /obj/item/assembly/health/examine(mob/user) . = ..() - . += "Use a multitool to swap between \"detect death\" mode and \"detect critical state\" mode." + . += "Use it in hand to turn it off/on and Alt-click to swap between \"detect death\" mode and \"detect critical state\" mode." + . += "[src.scanning ? "The sensor is on and you can see [health_scan] displayed on the screen" : "The sensor is off"]." /obj/item/assembly/health/activate() if(!..()) @@ -30,14 +30,13 @@ update_icon() return secured -/obj/item/assembly/health/multitool_act(mob/living/user, obj/item/I) +/obj/item/assembly/health/AltClick(mob/living/user) if(alarm_health == HEALTH_THRESHOLD_CRIT) alarm_health = HEALTH_THRESHOLD_DEAD to_chat(user, "You toggle [src] to \"detect death\" mode.") else alarm_health = HEALTH_THRESHOLD_CRIT to_chat(user, "You toggle [src] to \"detect critical state\" mode.") - return TRUE /obj/item/assembly/health/process() if(!scanning || !secured) @@ -46,7 +45,6 @@ var/atom/A = src if(connected && connected.holder) A = connected.holder - for(A, A && !ismob(A), A=A.loc); // like get_turf(), but for mobs. var/mob/living/M = A @@ -71,36 +69,7 @@ STOP_PROCESSING(SSobj, src) return -/obj/item/assembly/health/ui_interact(mob/user as mob)//TODO: Change this to the wires thingy +/obj/item/assembly/health/attack_self(mob/user) . = ..() - if(!secured) - user.show_message("The [name] is unsecured!") - return FALSE - var/dat = "Health Sensor" - dat += "
[scanning?"On":"Off"]" - if(scanning && health_scan) - dat += "
Health: [health_scan]" - user << browse(dat, "window=hscan") - onclose(user, "hscan") - -/obj/item/assembly/health/Topic(href, href_list) - ..() - if(!ismob(usr)) - return - - var/mob/user = usr - - if(!user.canUseTopic(src)) - usr << browse(null, "window=hscan") - onclose(usr, "hscan") - return - - if(href_list["scanning"]) - toggle_scan() - - if(href_list["close"]) - usr << browse(null, "window=hscan") - return - - attack_self(user) - return + to_chat(user, "You toggle [src] [src.scanning ? "off" : "on"].") + toggle_scan() diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index 33c6d46045..aa42d3e662 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -4,7 +4,10 @@ icon_state = "infrared" custom_materials = list(/datum/material/iron=1000, /datum/material/glass=500) is_position_sensitive = TRUE - + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 225 + var/ui_y = 110 var/on = FALSE var/visible = FALSE var/maxlength = 8 @@ -38,7 +41,7 @@ /obj/item/assembly/infra/activate() if(!..()) - return FALSE//Cooldown check + return FALSE //Cooldown check on = !on refreshBeam() update_icon() @@ -69,7 +72,7 @@ holder.update_icon() return -/obj/item/assembly/infra/dropped(mob/user) +/obj/item/assembly/infra/dropped() . = ..() if(holder) holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder @@ -133,7 +136,7 @@ . = ..() setDir(t) -/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback) +/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE) . = ..() olddir = dir @@ -176,55 +179,56 @@ return return refreshBeam() -/obj/item/assembly/infra/ui_interact(mob/user)//TODO: change this this to the wire control panel - . = ..() - if(is_secured(user)) - user.set_machine(src) - var/dat = "Infrared Laser" - dat += "
Status: [on ? "On" : "Off"]" - dat += "
Visibility: [visible ? "Visible" : "Invisible"]" - dat += "

Refresh" - dat += "

Close" - user << browse(dat, "window=infra") - onclose(user, "infra") - return - -/obj/item/assembly/infra/Topic(href, href_list) - ..() - if(usr.incapacitated() || !in_range(loc, usr)) - usr << browse(null, "window=infra") - onclose(usr, "infra") - return - if(href_list["state"]) - on = !(on) - update_icon() - refreshBeam() - if(href_list["visible"]) - visible = !(visible) - update_icon() - refreshBeam() - if(href_list["close"]) - usr << browse(null, "window=infra") - return - if(usr) - attack_self(usr) - /obj/item/assembly/infra/setDir() . = ..() refreshBeam() +/obj/item/assembly/infra/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/infra/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "InfraredEmitter", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/assembly/infra/ui_data(mob/user) + var/list/data = list() + data["on"] = on + data["visible"] = visible + return data + +/obj/item/assembly/infra/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + on = !on + . = TRUE + if("visibility") + visible = !visible + . = TRUE + + update_icon() + refreshBeam() + /***************************IBeam*********************************/ /obj/effect/beam/i_beam name = "infrared beam" icon = 'icons/obj/projectiles.dmi' icon_state = "ibeam" - var/obj/item/assembly/infra/master anchored = TRUE density = FALSE pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW + var/obj/item/assembly/infra/master /obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj) + . = ..() if(istype(AM, /obj/effect/beam)) return if (isitem(AM)) diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm index f1a4ce47cc..ef931279de 100644 --- a/code/modules/assembly/proximity.dm +++ b/code/modules/assembly/proximity.dm @@ -4,7 +4,10 @@ icon_state = "prox" custom_materials = list(/datum/material/iron=800, /datum/material/glass=200) attachable = TRUE - + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 250 + var/ui_y = 185 var/scanning = FALSE var/timing = FALSE var/time = 10 @@ -26,7 +29,7 @@ /obj/item/assembly/prox_sensor/activate() if(!..()) - return FALSE//Cooldown check + return FALSE //Cooldown check if(!scanning) timing = !timing else @@ -41,7 +44,6 @@ else proximity_monitor.SetHost(src,src) - /obj/item/assembly/prox_sensor/toggle_secure() secured = !secured if(!secured) @@ -56,8 +58,6 @@ update_icon() return secured - - /obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj) if (istype(AM, /obj/effect/beam)) return @@ -75,7 +75,6 @@ next_activate = world.time + 30 return TRUE - /obj/item/assembly/prox_sensor/process() if(!timing) return @@ -111,50 +110,47 @@ holder.update_icon() return -/obj/item/assembly/prox_sensor/ui_interact(mob/user)//TODO: Change this to the wires thingy - . = ..() +/obj/item/assembly/prox_sensor/ui_status(mob/user) if(is_secured(user)) - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = "Proximity Sensor" - if(!scanning) - dat += "
[(timing ? "Arming" : "Not Arming")] [minute]:[second]" - dat += "
- - + +" - dat += "
Armed":"1'>Unarmed (Movement sensor active when armed!)"]" - dat += "
Detection range: - [sensitivity] +" - dat += "

Refresh" - dat += "

Close" - user << browse(dat, "window=prox") - onclose(user, "prox") + return ..() + return UI_CLOSE + +/obj/item/assembly/prox_sensor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ProximitySensor", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/assembly/prox_sensor/ui_data(mob/user) + var/list/data = list() + data["seconds"] = round(time % 60) + data["minutes"] = round((time - data["seconds"]) / 60) + data["timing"] = timing + data["scanning"] = scanning + data["sensitivity"] = sensitivity + return data + +/obj/item/assembly/prox_sensor/ui_act(action, params) + if(..()) return - -/obj/item/assembly/prox_sensor/Topic(href, href_list) - ..() - if(usr.incapacitated() || !in_range(loc, usr)) - usr << browse(null, "window=prox") - onclose(usr, "prox") - return - - if(href_list["sense"]) - sensitivity_change(((href_list["sense"] == "up") ? 1 : -1)) - - if(href_list["scanning"]) - toggle_scan(text2num(href_list["scanning"])) - - if(href_list["time"]) - timing = text2num(href_list["time"]) - update_icon() - - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 0), 600) - - if(href_list["close"]) - usr << browse(null, "window=prox") - return - - if(usr) - attack_self(usr) - + switch(action) + if("scanning") + toggle_scan(!scanning) + . = TRUE + if("sense") + var/value = text2num(params["range"]) + if(value) + sensitivity_change(value) + . = TRUE + if("time") + timing = !timing + update_icon() + . = TRUE + if("input") + var/value = text2num(params["adjust"]) + if(value) + value = round(time + value) + time = clamp(value, 0, 600) + . = TRUE diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index cee0a9054c..178437c2ff 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -8,32 +8,49 @@ custom_materials = list(/datum/material/iron=400, /datum/material/glass=120) wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE attachable = TRUE - + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 280 + var/ui_y = 132 var/code = DEFAULT_SIGNALER_CODE var/frequency = FREQ_SIGNALER var/datum/radio_frequency/radio_connection - var/suicider = null + ///Holds the mind that commited suicide. + var/datum/mind/suicider + ///Holds a reference string to the mob, decides how much of a gamer you are. + var/suicide_mob var/hearing_range = 1 /obj/item/assembly/signaler/suicide_act(mob/living/carbon/user) user.visible_message("[user] eats \the [src]! If it is signaled, [user.p_they()] will die!") playsound(src, 'sound/items/eatfood.ogg', 50, TRUE) - user.transferItemToLoc(src, user, TRUE) - suicider = user - return MANUAL_SUICIDE + moveToNullspace() + suicider = user.mind + suicide_mob = REF(user) + return MANUAL_SUICIDE_NONLETHAL -/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user) - user.visible_message("[user]'s \the [src] receives a signal, killing [user.p_them()] instantly!") +/obj/item/assembly/signaler/proc/manual_suicide(datum/mind/suicidee) + var/mob/living/user = suicidee.current + if(!istype(user)) + return + if(suicide_mob == REF(user)) + user.visible_message("[user]'s [src] receives a signal, killing [user.p_them()] instantly!") + else + user.visible_message("[user]'s [src] receives a signal and [user.p_they()] die[user.p_s()] like a gamer!") user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something. user.death(0) + user.set_suicide(TRUE) + user.suicide_log() + playsound(user, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + qdel(src) /obj/item/assembly/signaler/Initialize() . = ..() set_frequency(frequency) - /obj/item/assembly/signaler/Destroy() SSradio.remove_object(src,frequency) + suicider = null . = ..() /obj/item/assembly/signaler/activate() @@ -47,14 +64,16 @@ holder.update_icon() return -/obj/item/assembly/signaler/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - if(!is_secured(user)) - return +/obj/item/assembly/signaler/ui_status(mob/user) + if(is_secured(user)) + return ..() + return UI_CLOSE + +/obj/item/assembly/signaler/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - var/ui_width = 280 - var/ui_height = 132 - ui = new(user, src, ui_key, "signaler", name, ui_width, ui_height, master_ui, state) + ui = new(user, src, ui_key, "Signaler", name, ui_x, ui_y, master_ui, state) ui.open() /obj/item/assembly/signaler/ui_data(mob/user) @@ -63,12 +82,12 @@ data["code"] = code data["minFrequency"] = MIN_FREE_FREQ data["maxFrequency"] = MAX_FREE_FREQ - return data /obj/item/assembly/signaler/ui_act(action, params) if(..()) return + switch(action) if("signal") INVOKE_ASYNC(src, .proc/signal) @@ -90,7 +109,7 @@ . = TRUE update_icon() - + /obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params) if(issignaler(W)) var/obj/item/assembly/signaler/signaler2 = W @@ -112,9 +131,6 @@ if(usr) GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]") - - return - /obj/item/assembly/signaler/receive_signal(datum/signal/signal) . = FALSE if(!signal) @@ -125,6 +141,7 @@ return if(suicider) manual_suicide(suicider) + return pulse(TRUE) audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range) for(var/CHM in get_hearers_in_view(hearing_range, src)) @@ -133,7 +150,6 @@ LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) return TRUE - /obj/item/assembly/signaler/proc/set_frequency(new_frequency) SSradio.remove_object(src, frequency) frequency = new_frequency @@ -162,7 +178,6 @@ return return ..(signal) - // Embedded signaller used in anomalies. /obj/item/assembly/signaler/anomaly name = "anomaly core" @@ -179,6 +194,8 @@ return FALSE if(signal.data["code"] != code) return FALSE + if(suicider) + manual_suicide(suicider) for(var/obj/effect/anomaly/A in get_turf(src)) A.anomalyNeutralize() return TRUE @@ -191,4 +208,4 @@ /obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params) return /obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) - return \ No newline at end of file + return diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm index bbcddbdb93..3224b2d373 100644 --- a/code/modules/assembly/timer.dm +++ b/code/modules/assembly/timer.dm @@ -4,7 +4,10 @@ icon_state = "timer" custom_materials = list(/datum/material/iron=500, /datum/material/glass=50) attachable = TRUE - + drop_sound = 'sound/items/handling/component_drop.ogg' + pickup_sound = 'sound/items/handling/component_pickup.ogg' + var/ui_x = 275 + var/ui_y = 115 var/timing = FALSE var/time = 5 var/saved_time = 5 @@ -41,7 +44,6 @@ update_icon() return TRUE - /obj/item/assembly/timer/toggle_secure() secured = !secured if(secured) @@ -52,7 +54,6 @@ update_icon() return secured - /obj/item/assembly/timer/proc/timer_end() if(!secured || next_activate > world.time) return FALSE @@ -66,7 +67,6 @@ timing = TRUE update_icon() - /obj/item/assembly/timer/process() if(!timing) return @@ -76,7 +76,6 @@ timer_end() time = saved_time - /obj/item/assembly/timer/update_icon() cut_overlays() attached_overlays = list() @@ -86,50 +85,44 @@ if(holder) holder.update_icon() - -/obj/item/assembly/timer/ui_interact(mob/user)//TODO: Have this use the wires - . = ..() +/obj/item/assembly/timer/ui_status(mob/user) if(is_secured(user)) - var/second = time % 60 - var/minute = (time - second) / 60 - var/dat = "Timing Unit" - dat += "
[(timing ? "Timing" : "Not Timing")] [minute]:[second]" - dat += "
- - + +" - dat += "

Stop repeating" : "1'>Set to repeat")]" - dat += "

Refresh" - dat += "

Close" - var/datum/browser/popup = new(user, "timer", name) - popup.set_content(dat) - popup.open() + return ..() + return UI_CLOSE +/obj/item/assembly/timer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Timer", name, ui_x, ui_y, master_ui, state) + ui.open() -/obj/item/assembly/timer/Topic(href, href_list) - ..() - if(!usr.canUseTopic(src, BE_CLOSE)) - usr << browse(null, "window=timer") - onclose(usr, "timer") +/obj/item/assembly/timer/ui_data(mob/user) + var/list/data = list() + data["seconds"] = round(time % 60) + data["minutes"] = round((time - data["seconds"]) / 60) + data["timing"] = timing + data["loop"] = loop + return data + +/obj/item/assembly/timer/ui_act(action, params) + if(..()) return - if(href_list["time"]) - timing = text2num(href_list["time"]) - if(timing && istype(holder, /obj/item/transfer_valve)) - var/timer_message = "[ADMIN_LOOKUPFLW(usr)] activated [src] attachment on [holder]." - message_admins(timer_message) - GLOB.bombers += timer_message - log_game("[key_name(usr)] activated [src] attachment on [holder]") - update_icon() - if(href_list["repeat"]) - loop = text2num(href_list["repeat"]) - - if(href_list["tp"]) - var/tp = text2num(href_list["tp"]) - time += tp - time = min(max(round(time), 1), 600) - saved_time = time - - if(href_list["close"]) - usr << browse(null, "window=timer") - return - - if(usr) - attack_self(usr) + switch(action) + if("time") + timing = !timing + if(timing && istype(holder, /obj/item/transfer_valve)) + log_bomber(usr, "activated a", src, "attachment on [holder]") + update_icon() + . = TRUE + if("repeat") + loop = !loop + . = TRUE + if("input") + var/value = text2num(params["adjust"]) + if(value) + value = round(time + value) + time = clamp(value, 1, 600) + saved_time = time + . = TRUE diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index a4617462fe..e25aa9b612 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -239,7 +239,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "airalarm", name, 440, 650, master_ui, state) + ui = new(user, src, ui_key, "AirAlarm", name, 440, 650, master_ui, state) ui.open() /obj/machinery/airalarm/ui_data(mob/user) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm index 02eb95acab..19711c6e5f 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm @@ -26,6 +26,21 @@ Passive gate is similar to the regular pump except: construction_type = /obj/item/pipe/directional pipe_state = "passivegate" + ui_x = 335 + ui_y = 115 + +/obj/machinery/atmospherics/components/binary/passive_gate/CtrlClick(mob/user) + if(can_interact(user)) + on = !on + update_icon() + return ..() + +/obj/machinery/atmospherics/components/binary/passive_gate/AltClick(mob/user) + if(can_interact(user)) + target_pressure = MAX_OUTPUT_PRESSURE + update_icon() + return ..() + /obj/machinery/atmospherics/components/binary/passive_gate/Destroy() SSradio.remove_object(src,frequency) return ..() @@ -91,7 +106,7 @@ Passive gate is similar to the regular pump except: datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_pump", name, 335, 115, master_ui, state) + ui = new(user, src, ui_key, "AtmosPump", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/atmospherics/components/binary/passive_gate/ui_data() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm index 9e49953df5..ec9b320d2c 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm @@ -111,7 +111,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_pump", name, 335, 115, master_ui, state) + ui = new(user, src, ui_key, "AtmosPump", name, 335, 115, master_ui, state) ui.open() /obj/machinery/atmospherics/components/binary/pump/ui_data() @@ -212,4 +212,4 @@ /obj/machinery/atmospherics/components/binary/pump/on/layer3 piping_layer = 3 - icon_state= "pump_on_map-3" \ No newline at end of file + icon_state= "pump_on_map-3" diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm index 864e3eef5e..d62d52ce43 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm @@ -96,7 +96,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_pump", name, 310, 115, master_ui, state) + ui = new(user, src, ui_key, "AtmosPump", name, 310, 115, master_ui, state) ui.open() /obj/machinery/atmospherics/components/binary/volume_pump/ui_data() @@ -200,4 +200,4 @@ /obj/machinery/atmospherics/components/binary/volume_pump/on/layer3 piping_layer = 3 - icon_state = "volpump_map-3" \ No newline at end of file + icon_state = "volpump_map-3" diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index 1099020662..068930ab6a 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -138,7 +138,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_filter", name, 475, 185, master_ui, state) + ui = new(user, src, ui_key, "AtmosFilter", name, 475, 185, master_ui, state) ui.open() /obj/machinery/atmospherics/components/trinary/filter/ui_data() @@ -280,4 +280,4 @@ critical_machine = TRUE /obj/machinery/atmospherics/components/trinary/filter/flipped/critical - critical_machine = TRUE \ No newline at end of file + critical_machine = TRUE diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm index 5b929452fe..a4519017a9 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm @@ -131,7 +131,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_mixer", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "AtmosMixer", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/atmospherics/components/trinary/mixer/ui_data() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index e013a86fd2..2a516b5877 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -317,7 +317,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state) + ui = new(user, src, ui_key, "Cryo", name, 400, 550, master_ui, state) ui.open() /obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index 87ab4fa643..e170109987 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -20,6 +20,21 @@ pipe_state = "injector" + ui_x = 310 + ui_y = 115 + +/obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user) + if(can_interact(user)) + on = !on + update_icon() + return ..() + +/obj/machinery/atmospherics/components/unary/outlet_injector/AltClick(mob/user) + if(can_interact(user)) + volume_rate = MAX_TRANSFER_RATE + update_icon() + return ..() + /obj/machinery/atmospherics/components/unary/outlet_injector/Destroy() SSradio.remove_object(src,frequency) return ..() @@ -140,7 +155,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "atmos_pump", name, 310, 115, master_ui, state) + ui = new(user, src, ui_key, "AtmosPump", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/atmospherics/components/unary/outlet_injector/ui_data() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm index 877826c1c1..db49efc781 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm @@ -128,7 +128,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "thermomachine", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "ThermoMachine", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/atmospherics/components/unary/thermomachine/ui_data(mob/user) diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 0b26cfc2f0..8dcf7e7b16 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -321,7 +321,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "canister", name, 420, 405, master_ui, state) + ui = new(user, src, ui_key, "Canister", name, 420, 405, master_ui, state) ui.open() /obj/machinery/portable_atmospherics/canister/ui_data() diff --git a/code/modules/atmospherics/machinery/portable/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm index 3603e46490..9e2456cc2d 100644 --- a/code/modules/atmospherics/machinery/portable/pump.dm +++ b/code/modules/atmospherics/machinery/portable/pump.dm @@ -8,6 +8,8 @@ name = "portable air pump" icon_state = "psiphon:0" density = TRUE + ui_x = 300 + ui_y = 315 var/on = FALSE var/direction = PUMP_OUT @@ -32,7 +34,6 @@ /obj/machinery/portable_atmospherics/pump/update_icon_state() icon_state = "psiphon:[on]" - /obj/machinery/portable_atmospherics/pump/update_overlays() . = ..() if(holding) @@ -79,14 +80,14 @@ on = FALSE update_icon() else if(on && holding && direction == PUMP_OUT) - investigate_log("[key_name(user)] started a transfer into [holding].
", INVESTIGATE_ATMOS) + investigate_log("[key_name(user)] started a transfer into [holding].", INVESTIGATE_ATMOS) /obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "portable_pump", name, 300, 315, master_ui, state) + ui = new(user, src, ui_key, "PortablePump", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/portable_atmospherics/pump/ui_data() @@ -121,14 +122,14 @@ message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]") log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]") else if(on && direction == PUMP_OUT) - investigate_log("[key_name(usr)] started a transfer into [holding].
", INVESTIGATE_ATMOS) + investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) . = TRUE if("direction") if(direction == PUMP_OUT) direction = PUMP_IN else if(on && holding) - investigate_log("[key_name(usr)] started a transfer into [holding].
", INVESTIGATE_ATMOS) + investigate_log("[key_name(usr)] started a transfer into [holding].", INVESTIGATE_ATMOS) direction = PUMP_OUT . = TRUE if("pressure") @@ -142,19 +143,14 @@ else if(pressure == "max") pressure = PUMP_MAX_PRESSURE . = TRUE - else if(pressure == "input") - pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null - if(!isnull(pressure) && !..()) - . = TRUE else if(text2num(pressure) != null) pressure = text2num(pressure) . = TRUE if(.) - pump.target_pressure = CLAMP(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) + pump.target_pressure = clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE) investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS) if("eject") if(holding) - holding.forceMove(drop_location()) - holding = null + replace_tank(usr, FALSE) . = TRUE update_icon() diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm index 3dfce7c1bf..f4556750c8 100644 --- a/code/modules/atmospherics/machinery/portable/scrubber.dm +++ b/code/modules/atmospherics/machinery/portable/scrubber.dm @@ -2,6 +2,8 @@ name = "portable air scrubber" icon_state = "pscrubber:0" density = TRUE + ui_x = 320 + ui_y = 350 var/on = FALSE var/volume_rate = 1000 @@ -71,7 +73,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "portable_scrubber", name, 320, 335, master_ui, state) + ui = new(user, src, ui_key, "PortableScrubber", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/portable_atmospherics/scrubber/ui_data() diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm index 4f155d4174..6ddda43bc8 100644 --- a/code/modules/awaymissions/gateway.dm +++ b/code/modules/awaymissions/gateway.dm @@ -1,246 +1,327 @@ +/// Station home gateway GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation) +/// List of possible gateway destinations. +GLOBAL_LIST_EMPTY(gateway_destinations) + +/** + * Corresponds to single entry in gateway control. + * + * Will NOT be added automatically to GLOB.gateway_destinations list. + */ +/datum/gateway_destination + var/name = "Unknown Destination" + var/wait = 0 /// How long after roundstart this destination becomes active + var/enabled = TRUE /// If disabled, the destination won't be availible + var/hidden = FALSE /// Will not show on gateway controls at all. + +/* Can a gateway link to this destination right now. */ +/datum/gateway_destination/proc/is_availible() + return enabled && (world.time - SSticker.round_start_time >= wait) + +/* Returns user-friendly description why you can't connect to this destination, displayed in UI */ +/datum/gateway_destination/proc/get_availible_reason() + . = "Unreachable" + if(world.time - SSticker.round_start_time < wait) + . = "Connection desynchronized. Recalibration in progress." + +/* Check if the movable is allowed to arrive at this destination (exile implants mostly) */ +/datum/gateway_destination/proc/incoming_pass_check(atom/movable/AM) + return TRUE + +/* Get the actual turf we'll arrive at */ +/datum/gateway_destination/proc/get_target_turf() + CRASH("get target turf not implemented for this destination type") + +/* Called after moving the movable to target turf */ +/datum/gateway_destination/proc/post_transfer(atom/movable/AM) + if (ismob(AM)) + var/mob/M = AM + if (M.client) + M.client.move_delay = max(world.time + 5, M.client.move_delay) + +/* Called when gateway activates with this destination. */ +/datum/gateway_destination/proc/activate(obj/machinery/gateway/activated) + return + +/* Called when gateway targeting this destination deactivates. */ +/datum/gateway_destination/proc/deactivate(obj/machinery/gateway/deactivated) + return + +/* Returns data used by gateway controller ui */ +/datum/gateway_destination/proc/get_ui_data() + . = list() + .["ref"] = REF(src) + .["name"] = name + .["availible"] = is_availible() + .["reason"] = get_availible_reason() + if(wait) + .["timeout"] = max(1 - (wait - (world.time - SSticker.round_start_time)) / wait, 0) + +/* Destination is another gateway */ +/datum/gateway_destination/gateway + /// The gateway this destination points at + var/obj/machinery/gateway/target_gateway + +/* We set the target gateway target to activator gateway */ +/datum/gateway_destination/gateway/activate(obj/machinery/gateway/activated) + if(!target_gateway.target) + target_gateway.activate(activated) + +/* We turn off the target gateway if it's linked with us */ +/datum/gateway_destination/gateway/deactivate(obj/machinery/gateway/deactivated) + if(target_gateway.target == deactivated.destination) + target_gateway.deactivate() + +/datum/gateway_destination/gateway/is_availible() + return ..() && target_gateway.calibrated && !target_gateway.target && target_gateway.powered() + +/datum/gateway_destination/gateway/get_availible_reason() + . = ..() + if(!target_gateway.calibrated) + . = "Exit gateway malfunction. Manual recalibration required." + if(target_gateway.target) + . = "Exit gateway in use." + if(!target_gateway.powered()) + . = "Exit gateway unpowered." + +/datum/gateway_destination/gateway/get_target_turf() + return get_step(target_gateway.portal,SOUTH) + +/datum/gateway_destination/gateway/post_transfer(atom/movable/AM) + . = ..() + addtimer(CALLBACK(AM,/atom/movable.proc/setDir,SOUTH),0) + +/* Special home destination, so we can check exile implants */ +/datum/gateway_destination/gateway/home + +/datum/gateway_destination/gateway/home/incoming_pass_check(atom/movable/AM) + if(isliving(AM)) + if(check_exile_implant(AM)) + return FALSE + else + for(var/mob/living/L in AM.contents) + if(check_exile_implant(L)) + target_gateway.say("Rejecting [AM]: Exile implant detected in contained lifeform.") + return FALSE + if(AM.has_buckled_mobs()) + for(var/mob/living/L in AM.buckled_mobs) + if(check_exile_implant(L)) + target_gateway.say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") + return FALSE + return TRUE + +/datum/gateway_destination/gateway/home/proc/check_exile_implant(mob/living/L) + for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant + to_chat(L, "The station gate has detected your exile implant and is blocking your entry.") + return TRUE + return FALSE + + +/* Destination is one ore more turfs - created by landmarks */ +/datum/gateway_destination/point + var/list/target_turfs = list() + /// Used by away landmarks + var/id + +/datum/gateway_destination/point/get_target_turf() + return pick(target_turfs) + +/* Dense invisible object starting the teleportation. Created by gateways on activation. */ +/obj/effect/gateway_portal_bumper + var/obj/machinery/gateway/gateway + density = TRUE + invisibility = INVISIBILITY_ABSTRACT + +/obj/effect/gateway_portal_bumper/Bumped(atom/movable/AM) + if(get_dir(src,AM) == SOUTH) + gateway.Transfer(AM) + +/obj/effect/gateway_portal_bumper/Destroy(force) + . = ..() + gateway = null /obj/machinery/gateway name = "gateway" desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations." icon = 'icons/obj/machines/gateway.dmi' icon_state = "off" - density = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/active = 0 - var/checkparts = TRUE - var/list/obj/effect/landmark/randomspawns = list() + + // 3x2 offset by one row + pixel_x = -32 + pixel_y = -32 + bound_height = 64 + bound_width = 96 + bound_x = -32 + bound_y = 0 + density = TRUE + + use_power = IDLE_POWER_USE + idle_power_usage = 100 + active_power_usage = 5000 + var/calibrated = TRUE - var/list/linked = list() - var/can_link = FALSE //Is this the centerpiece? + /// Type of instanced gateway destination, needs to be subtype of /datum/gateway_destination/gateway + var/destination_type = /datum/gateway_destination/gateway + /// Name of the generated destination + var/destination_name = "Unknown Gateway" + /// This is our own destination, pointing at this gateway + var/datum/gateway_destination/gateway/destination + /// This is current active destination + var/datum/gateway_destination/target + /// bumper object, the thing that starts actual teleport + var/obj/effect/gateway_portal_bumper/portal /obj/machinery/gateway/Initialize() - randomspawns = GLOB.awaydestinations + generate_destination() update_icon() - if(!istype(src, /obj/machinery/gateway/centerstation) && !istype(src, /obj/machinery/gateway/centeraway)) - switch(dir) - if(SOUTH,SOUTHEAST,SOUTHWEST) - density = FALSE return ..() -/obj/machinery/gateway/proc/toggleoff() - for(var/obj/machinery/gateway/G in linked) - G.active = 0 - G.update_icon() - active = 0 +/obj/machinery/gateway/proc/generate_destination() + destination = new destination_type + destination.name = destination_name + destination.target_gateway = src + GLOB.gateway_destinations += destination + +/obj/machinery/gateway/proc/deactivate() + var/datum/gateway_destination/dest = target + target = null + dest.deactivate(src) + QDEL_NULL(portal) + use_power = IDLE_POWER_USE update_icon() -/obj/machinery/gateway/proc/detect() - if(!can_link) - return FALSE - linked = list() //clear the list - var/turf/T = loc - var/ready = FALSE - - for(var/i in GLOB.alldirs) - T = get_step(loc, i) - var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T - if(G) - linked.Add(G) - continue - - //this is only done if we fail to find a part - ready = FALSE - toggleoff() - break - - if((linked.len == 8) || !checkparts) - ready = TRUE - return ready +/obj/machinery/gateway/process() + if((stat & (NOPOWER)) && use_power) + if(target) + deactivate() + return /obj/machinery/gateway/update_icon_state() - icon_state = active ? "on" : "off" + if(target) + icon_state = "on" + else + icon_state = "off" -/obj/machinery/gateway/attack_hand(mob/user) - . = ..() - if(.) - return - if(!detect()) - return - if(!active) - toggleon(user) - return - toggleoff() - -/obj/machinery/gateway/proc/toggleon(mob/user) - return FALSE - -/obj/machinery/gateway/safe_throw_at() +/obj/machinery/gateway/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) return +/obj/machinery/gateway/proc/generate_bumper() + portal = new(get_turf(src)) + portal.gateway = src + +/obj/machinery/gateway/proc/activate(datum/gateway_destination/D) + if(!powered() || target) + return + target = D + target.activate(destination) + generate_bumper() + use_power = ACTIVE_POWER_USE + update_icon() + +/obj/machinery/gateway/proc/Transfer(atom/movable/AM) + if(!target || !target.incoming_pass_check(AM)) + return + AM.forceMove(target.get_target_turf()) + target.post_transfer(AM) + +/* Station's primary gateway */ +/obj/machinery/gateway/centerstation + destination_type = /datum/gateway_destination/gateway/home + destination_name = "Home Gateway" + /obj/machinery/gateway/centerstation/Initialize() . = ..() if(!GLOB.the_gateway) GLOB.the_gateway = src - update_icon() - wait = world.time + CONFIG_GET(number/gateway_delay) //+ thirty minutes default - awaygate = locate(/obj/machinery/gateway/centeraway) /obj/machinery/gateway/centerstation/Destroy() if(GLOB.the_gateway == src) GLOB.the_gateway = null return ..() -//this is da important part wot makes things go -/obj/machinery/gateway/centerstation - density = TRUE - icon_state = "offcenter" - use_power = IDLE_POWER_USE - - //warping vars - var/wait = 0 //this just grabs world.time at world start - var/obj/machinery/gateway/centeraway/awaygate = null - can_link = TRUE - -/obj/machinery/gateway/centerstation/update_icon_state() - icon_state = active ? "oncenter" : "offcenter" - -/obj/machinery/gateway/centerstation/process() - if((stat & (NOPOWER)) && use_power) - if(active) - toggleoff() - return - - if(active) - use_power(5000) - -/obj/machinery/gateway/centerstation/toggleon(mob/user) - if(!detect()) - return - if(!powered()) - return - if(!awaygate) - to_chat(user, "Error: No destination found.") - return - if(world.time < wait) - to_chat(user, "Error: Warpspace triangulation in progress. Estimated time to completion: [DisplayTimeText(wait - world.time)].") - return - - for(var/obj/machinery/gateway/G in linked) - G.active = 1 - G.update_icon() - active = 1 - update_icon() - -//okay, here's the good teleporting stuff -/obj/machinery/gateway/centerstation/Bumped(atom/movable/AM) - if(!active) - return - if(!detect()) - return - if(!awaygate || QDELETED(awaygate)) - return - - if(awaygate.calibrated) - AM.forceMove(get_step(awaygate.loc, SOUTH)) - AM.setDir(SOUTH) - if (ismob(AM)) - var/mob/M = AM - if (M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - return +/obj/machinery/gateway/multitool_act(mob/living/user, obj/item/I) + if(calibrated) + to_chat(user, "The gate is already calibrated, there is no work for you to do here.") else - var/obj/effect/landmark/dest = pick(randomspawns) - if(dest) - AM.forceMove(get_turf(dest)) - AM.setDir(SOUTH) - use_power(5000) - return - -/obj/machinery/gateway/centeraway/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/multitool)) - if(calibrated) - to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.") - return - else - to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") - calibrated = TRUE - return - -/////////////////////////////////////Away//////////////////////// - - -/obj/machinery/gateway/centeraway - density = TRUE - icon_state = "offcenter" - use_power = NO_POWER_USE - var/obj/machinery/gateway/centerstation/stationgate = null - can_link = TRUE - - -/obj/machinery/gateway/centeraway/Initialize() - . = ..() - update_icon() - stationgate = locate(/obj/machinery/gateway/centerstation) - - -/obj/machinery/gateway/centeraway/update_icon_state() - icon_state = active ? "oncenter" : "offcenter" - -/obj/machinery/gateway/centeraway/toggleon(mob/user) - if(!detect()) - return - if(!stationgate) - to_chat(user, "Error: No destination found.") - return - - for(var/obj/machinery/gateway/G in linked) - G.active = 1 - G.update_icon() - active = 1 - update_icon() - -/obj/machinery/gateway/centeraway/proc/check_exile_implant(mob/living/L) - for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant - to_chat(L, "\black The station gate has detected your exile implant and is blocking your entry.") - return TRUE - return FALSE - -/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM) - if(!detect()) - return - if(!active) - return - if(!stationgate || QDELETED(stationgate)) - return - if(isliving(AM)) - if(check_exile_implant(AM)) - return - else - for(var/mob/living/L in AM.contents) - if(check_exile_implant(L)) - say("Rejecting [AM]: Exile implant detected in contained lifeform.") - return - if(AM.has_buckled_mobs()) - for(var/mob/living/L in AM.buckled_mobs) - if(check_exile_implant(L)) - say("Rejecting [AM]: Exile implant detected in close proximity lifeform.") - return - AM.forceMove(get_step(stationgate.loc, SOUTH)) - AM.setDir(SOUTH) - if (ismob(AM)) - var/mob/M = AM - if (M.client) - M.client.move_delay = max(world.time + 5, M.client.move_delay) - - -/obj/machinery/gateway/centeraway/admin - desc = "A mysterious gateway built by unknown hands, this one seems more compact." - -/obj/machinery/gateway/centeraway/admin/Initialize() - . = ..() - if(stationgate && !stationgate.awaygate) - stationgate.awaygate = src - -/obj/machinery/gateway/centeraway/admin/detect() + to_chat(user, "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.") + calibrated = TRUE return TRUE +/* Doesn't need control console or power, always links to home when interacting. */ +/obj/machinery/gateway/away + density = TRUE + use_power = NO_POWER_USE + +/obj/machinery/gateway/away/interact(mob/user, special_state) + . = ..() + if(!target) + if(!GLOB.the_gateway) + to_chat(user,"Home gateway is not responding!") + if(GLOB.the_gateway.target) + to_chat(user,"Home gateway already in use!") + return + activate(GLOB.the_gateway.destination) + else + deactivate() + +/* Gateway control computer */ +/obj/machinery/computer/gateway_control + name = "Gateway Control" + desc = "Human friendly interface to the mysterious gate next to it." + var/obj/machinery/gateway/G + +/obj/machinery/computer/gateway_control/Initialize(mapload, obj/item/circuitboard/C) + . = ..() + try_to_linkup() + +/obj/machinery/computer/gateway_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state = GLOB.default_state) + . = ..() + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Gateway", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/computer/gateway_control/ui_data(mob/user) + . = ..() + .["gateway_present"] = G + .["gateway_status"] = G ? G.powered() : FALSE + .["current_target"] = G?.target?.get_ui_data() + var/list/destinations = list() + if(G) + for(var/datum/gateway_destination/D in GLOB.gateway_destinations) + if(D == G.destination) + continue + destinations += list(D.get_ui_data()) + .["destinations"] = destinations + +/obj/machinery/computer/gateway_control/ui_act(action, list/params) + . = ..() + if(.) + return + switch(action) + if("linkup") + try_to_linkup() + return TRUE + if("activate") + var/datum/gateway_destination/D = locate(params["destination"]) in GLOB.gateway_destinations + try_to_connect(D) + return TRUE + if("deactivate") + if(G && G.target) + G.deactivate() + return TRUE + +/obj/machinery/computer/gateway_control/proc/try_to_linkup() + G = locate(/obj/machinery/gateway) in view(7,get_turf(src)) + +/obj/machinery/computer/gateway_control/proc/try_to_connect(datum/gateway_destination/D) + if(!D || !G) + return + if(!D.is_availible() || G.target) + return + G.activate(D) /obj/item/paper/fluff/gateway - info = "Congratulations,

Your station has been selected to carry out the Gateway Project.

The equipment will be shipped to you at the start of the next quarter.
You are to prepare a secure location to house the equipment as outlined in the attached documents.

--Nanotrasen Blue Space Research" + info = "Congratulations,

Your station has been selected to carry out the Gateway Project.

The equipment will be shipped to you at the start of the next quarter.
You are to prepare a secure location to house the equipment as outlined in the attached documents.

--Nanotrasen Bluespace Research" name = "Confidential Correspondence, Pg 1" diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 3418f78dbd..f33ea7059b 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -55,7 +55,7 @@ force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.adm ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "centcom_podlauncher", "Config/Launch Supplypod", 700, 700, master_ui, state) + ui = new(user, src, ui_key, "CentcomPodLauncher", "Config/Launch Supplypod", 700, 700, master_ui, state) ui.open() /datum/centcom_podlauncher/ui_data(mob/user) //Sends info about the pod to the UI. diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index 64b208a90b..0f0a7975ed 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -68,15 +68,18 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "cargo", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "Cargo", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/cargo/ui_data() var/list/data = list() data["location"] = SSshuttle.supply.getStatusText() + /*var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance*/ data["away"] = SSshuttle.supply.getDockedId() == "supply_away" + //data["self_paid"] = self_paid data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE - data["points"] = SSshuttle.points data["loan"] = !!SSshuttle.shuttle_loan data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched var/message = "Remember to stamp and send back the supply manifests." @@ -92,6 +95,7 @@ "cost" = SO.pack.cost, "id" = SO.id, "orderer" = SO.orderer, + "paid" = !isnull(SO.paying_account) //paid by requester )) data["requests"] = list() @@ -124,6 +128,7 @@ "cost" = P.cost, "id" = pack, "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. + //"small_item" = P.small_item, "access" = P.access )) return data @@ -162,6 +167,8 @@ else SSshuttle.shuttle_loan.loan_shuttle() say("The supply shuttle has been loaned to CentCom.") + investigate_log("[key_name(usr)] accepted a shuttle loan event.", INVESTIGATE_CARGO) + log_game("[key_name(usr)] accepted a shuttle loan event.") . = TRUE if("add") var/id = text2path(params["id"]) @@ -182,19 +189,36 @@ name = usr.real_name rank = "Silicon" + /* var/datum/bank_account/account + if(self_paid && ishuman(usr)) + var/mob/living/carbon/human/H = usr + var/obj/item/card/id/id_card = H.get_idcard(TRUE) + if(!istype(id_card)) + say("No ID card detected.") + return + account = id_card.registered_account + if(!istype(account)) + say("Invalid bank account.") + return + */ var/reason = "" - if(requestonly) + if(requestonly && !self_paid) reason = stripped_input("Reason:", name, "") if(isnull(reason) || ..()) return var/turf/T = get_turf(src) - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account) SO.generateRequisition(T) - if(requestonly) + if(requestonly && !self_paid) SSshuttle.requestlist += SO else SSshuttle.shoppinglist += SO + if(self_paid) + say("Order processed. The price will be charged to [account.account_holder]'s bank account on delivery.") + if(requestonly && message_cooldown < world.time) + radio.talk_into(src, "A new order has been requested.", RADIO_CHANNEL_SUPPLY) + message_cooldown = world.time + 30 SECONDS . = TRUE if("remove") var/id = text2num(params["id"]) @@ -224,6 +248,9 @@ if("denyall") SSshuttle.requestlist.Cut() . = TRUE + /* if("toggleprivate") + self_paid = !self_paid + */ . = TRUE if(.) post_signal("supply") diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index a65d8cad40..3761a34f73 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -1,5 +1,5 @@ #define MAX_EMAG_ROCKETS 8 -#define BEACON_COST 5000 +#define BEACON_COST 500 #define SP_LINKED 1 #define SP_READY 2 #define SP_LAUNCH 3 @@ -13,8 +13,11 @@ All sales are near instantaneous - please choose carefully" icon_screen = "supply_express" circuit = /obj/item/circuitboard/computer/cargo/express + ui_x = 600 + ui_y = 700 blockade_warning = "Bluespace instability detected. Delivery impossible." req_access = list(ACCESS_QM) + var/message var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names. var/list/meme_pack_data @@ -40,7 +43,7 @@ to_chat(user, "You [locked ? "lock" : "unlock"] the interface.") return else if(istype(W, /obj/item/disk/cargo/bluespace_pod)) - podType = /obj/structure/closet/supplypod/bluespacepod + podType = /obj/structure/closet/supplypod/bluespacepod//doesnt effect circuit board, making reversal possible to_chat(user, "You insert the disk into [src], allowing for advanced supply delivery vehicles.") qdel(W) return TRUE @@ -50,22 +53,20 @@ sb.link_console(src, user) return TRUE else - to_chat(user, "[src] is already linked to [sb].") + to_chat(user, "[src] is already linked to [sb].") ..() /obj/machinery/computer/cargo/express/emag_act(mob/living/user) - . = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) if(obj_flags & EMAGGED) return - user.visible_message("[user] swipes a suspicious card through [src]!", - "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") + if(user) + user.visible_message("[user] swipes a suspicious card through [src]!", + "You change the routing protocols, allowing the Supply Pod to land anywhere on the station.") obj_flags |= EMAGGED // This also sets this on the circuit board var/obj/item/circuitboard/computer/cargo/board = circuit board.obj_flags |= EMAGGED packin_up() - req_access = list() - return TRUE /obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry meme_pack_data = list() // sorry for what? @@ -90,23 +91,25 @@ /obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "cargo_express", name, 600, 700, master_ui, state) + ui = new(user, src, ui_key, "CargoExpress", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/cargo/express/ui_data(mob/user) var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? var/list/data = list() + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + data["points"] = D.account_balance data["locked"] = locked//swipe an ID to unlock - data["siliconUser"] = hasSiliconAccessInArea(user) + data["siliconUser"] = user.has_unlimited_silicon_privilege data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay? data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location? - data["canBuyBeacon"] = cooldown <= 0 && SSshuttle.points >= BEACON_COST + data["canBuyBeacon"] = cooldown <= 0 && D.account_balance >= BEACON_COST data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary data["hasBeacon"] = beacon != null//is there a linked beacon? data["beaconName"] = beacon ? beacon.name : "No Beacon Found" data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons - data["points"] = SSshuttle.points data["supplies"] = list() message = "Sales are near-instantaneous - please choose carefully." if(SSshuttle.supplyBlocked) @@ -137,13 +140,15 @@ if (beacon) beacon.update_status(SP_READY) //turns on the beacon's ready light if("printBeacon") - if (SSshuttle.points >= BEACON_COST) - cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam - var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) - C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) - printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 - beacon.name = "Supply Pod Beacon #[printed_beacons]" - SSshuttle.points -= BEACON_COST + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + if(D.adjust_money(-BEACON_COST)) + cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam + var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location()) + C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc) + printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1 + beacon.name = "Supply Pod Beacon #[printed_beacons]" + if("add")//Generate Supply Order first var/id = text2path(params["id"]) @@ -163,8 +168,12 @@ var/reason = "" var/list/empty_turfs var/datum/supply_order/SO = new(pack, name, rank, ckey, reason) + var/points_to_check + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + points_to_check = D.account_balance if(!(obj_flags & EMAGGED)) - if(SO.pack.cost <= SSshuttle.points) + if(SO.pack.cost <= points_to_check) var/LZ if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay LZ = get_turf(beacon) @@ -181,14 +190,13 @@ CHECK_TICK if(empty_turfs && empty_turfs.len) LZ = pick(empty_turfs) - if (SO.pack.cost <= SSshuttle.points && LZ)//we need to call the cost check again because of the CHECK_TICK call - SSshuttle.points -= SO.pack.cost - SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.cost]", "[SO.pack.name]")) - new /obj/effect/abstract/DPtarget(LZ, podType, SO) + if (SO.pack.cost <= points_to_check && LZ)//we need to call the cost check again because of the CHECK_TICK call + D.adjust_money(-SO.pack.cost) + new /obj/effect/DPtarget(LZ, podType, SO) . = TRUE update_icon() else - if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= SSshuttle.points) // bulk discount :^) + if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= points_to_check) // bulk discount :^) landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone for(var/turf/open/floor/T in landingzone.contents) if(is_blocked_turf(T)) @@ -196,13 +204,13 @@ LAZYADD(empty_turfs, T) CHECK_TICK if(empty_turfs && empty_turfs.len) - SSshuttle.points -= SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) - SSblackbox.record_feedback("nested tally", "cargo_imports", MAX_EMAG_ROCKETS, list("[SO.pack.cost * 0.72]", "[SO.pack.name]")) + D.adjust_money(-(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS))) + SO.generateRequisition(get_turf(src)) for(var/i in 1 to MAX_EMAG_ROCKETS) var/LZ = pick(empty_turfs) LAZYREMOVE(empty_turfs, LZ) - new /obj/effect/abstract/DPtarget(LZ, podType, SO) + new /obj/effect/DPtarget(LZ, podType, SO) . = TRUE update_icon() CHECK_TICK diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm index 86ff1ed0da..464db74c9e 100644 --- a/code/modules/events/pirates.dm +++ b/code/modules/events/pirates.dm @@ -5,7 +5,7 @@ max_occurrences = 1 min_players = 10 earliest_start = 30 MINUTES - gamemode_blacklist = list("nuclear","dynamic") + gamemode_blacklist = list("nuclear") /datum/round_event_control/pirates/preRunEvent() if (!SSmapping.empty_space) @@ -15,8 +15,9 @@ /datum/round_event/pirates startWhen = 60 //2 minutes to answer - var/datum/comm_message/threat_message + var/datum/comm_message/threat var/payoff = 0 + var/payoff_min = 20000 var/paid_off = FALSE var/ship_name = "Space Privateers Association" var/shuttle_spawned = FALSE @@ -25,30 +26,38 @@ ship_name = pick(strings(PIRATE_NAMES_FILE, "ship_names")) /datum/round_event/pirates/announce(fake) - priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport") // CITADEL EDIT metabreak + priority_announce("Incoming subspace communication. Secure channel opened at all communication consoles.", "Incoming Message", 'sound/ai/commandreport.ogg') if(fake) return - threat_message = new - payoff = round(SSshuttle.points * 0.80) - threat_message.title = "Business proposition" - threat_message.content = "This is [ship_name]. Pay up [payoff] credits or you'll walk the plank." - threat_message.possible_answers = list("We'll pay.","No way.") - threat_message.answer_callback = CALLBACK(src,.proc/answered) - SScommunications.send_message(threat_message,unique = TRUE) + threat = new + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + payoff = max(payoff_min, FLOOR(D.account_balance * 0.80, 1000)) + threat.title = "Business proposition" + threat.content = "This is [ship_name]. Pay up [payoff] credits or you'll walk the plank." + threat.possible_answers = list("We'll pay.","No way.") + threat.answer_callback = CALLBACK(src,.proc/answered) + SScommunications.send_message(threat,unique = TRUE) /datum/round_event/pirates/proc/answered() - if(threat_message && threat_message.answered == 1) - if(SSshuttle.points >= payoff) - SSshuttle.points -= payoff - priority_announce("Thanks for the credits, landlubbers.",sender_override = ship_name) - paid_off = TRUE - return - else - priority_announce("Trying to cheat us? You'll regret this!",sender_override = ship_name) + if(threat && threat.answered == 1) + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + if(D.adjust_money(-payoff)) + priority_announce("Thanks for the credits, landlubbers.", sender_override = ship_name) + paid_off = TRUE + return + else + priority_announce("Trying to cheat us? You'll regret this!", sender_override = ship_name) if(!shuttle_spawned) spawn_shuttle() + else + priority_announce("Too late to beg for mercy!", sender_override = ship_name) /datum/round_event/pirates/start() + if(threat && !threat.answered) + threat.possible_answers = list("Too late") + threat.answered = 1 if(!paid_off && !shuttle_spawned) spawn_shuttle() @@ -59,8 +68,8 @@ shuffle_inplace(candidates) var/datum/map_template/shuttle/pirate/default/ship = new - var/x = rand(TRANSITIONEDGE,world.maxx - TRANSITIONEDGE - ship.width) - var/y = rand(TRANSITIONEDGE,world.maxy - TRANSITIONEDGE - ship.height) + var/x = rand(TRANSITIONEDGE, world.maxx - TRANSITIONEDGE - ship.width) + var/y = rand(TRANSITIONEDGE, world.maxy - TRANSITIONEDGE - ship.height) var/z = SSmapping.empty_space.z_value var/turf/T = locate(x,y,z) if(!T) @@ -68,16 +77,18 @@ if(!ship.load(T)) CRASH("Loading pirate ship failed!") + for(var/turf/A in ship.get_affected_turfs(T)) for(var/obj/effect/mob_spawn/human/pirate/spawner in A) if(candidates.len > 0) var/mob/M = candidates[1] spawner.create(M.ckey) candidates -= M + announce_to_ghosts(M) else - notify_ghosts("Space pirates are waking up!", source = spawner, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_dnr_observers = TRUE) + announce_to_ghosts(spawner) - priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", "commandreport") //CITADEL EDIT also metabreak here too + priority_announce("Unidentified armed ship detected near the station.") //Shuttle equipment @@ -94,16 +105,16 @@ /obj/machinery/shuttle_scrambler/Initialize(mapload) . = ..() - gps = new/obj/item/gps/internal/pirate(src) - gps.tracking = FALSE update_icon() /obj/machinery/shuttle_scrambler/process() if(active) if(is_station_level(z)) - var/siphoned = min(SSshuttle.points,siphon_per_tick) - SSshuttle.points -= siphoned - credits_stored += siphoned + var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + if(D) + var/siphoned = min(D.account_balance,siphon_per_tick) + D.adjust_money(-siphoned) + credits_stored += siphoned interrupt_research() else return @@ -122,7 +133,7 @@ if(!active) if(alert(user, "Turning the scrambler on will make the shuttle trackable by GPS. Are you sure you want to do it?", "Scrambler", "Yes", "Cancel") == "Cancel") return - if(active || !user.canUseTopic(src)) + if(active || !user.canUseTopic(src, BE_CLOSE)) return toggle_on(user) update_icon() @@ -139,14 +150,12 @@ new /obj/effect/temp_visual/emp(get_turf(S)) /obj/machinery/shuttle_scrambler/proc/dump_loot(mob/user) - if(credits_stored < 200) - to_chat(user,"Not enough credits to retrieve.") - return - while(credits_stored >= 200) - new /obj/item/stack/spacecash/c200(drop_location()) - credits_stored -= 200 - to_chat(user,"You retrieve the siphoned credits!") - credits_stored = 0 + if(credits_stored) // Prevents spamming empty holochips + new /obj/item/holochip(drop_location(), credits_stored) + to_chat(user,"You retrieve the siphoned credits!") + credits_stored = 0 + else + to_chat(user,"There's nothing to withdraw.") /obj/machinery/shuttle_scrambler/proc/send_notification() priority_announce("Data theft signal detected, source registered on local gps units.") @@ -171,7 +180,6 @@ /obj/item/gps/internal/pirate gpstag = "Nautical Signal" desc = "You can hear shanties over the static." - /obj/machinery/computer/shuttle/pirate name = "pirate shuttle console" shuttleId = "pirateship" @@ -195,40 +203,19 @@ /obj/docking_port/mobile/pirate name = "pirate shuttle" id = "pirateship" - var/engines_cooling = FALSE - var/engine_cooldown = 3 MINUTES - -/obj/docking_port/mobile/pirate/getStatusText() - . = ..() - if(engines_cooling) - return "[.] - Engines cooling." - -/obj/docking_port/mobile/pirate/initiate_docking(obj/docking_port/stationary/new_dock, movement_direction, force=FALSE) - . = ..() - if(. == DOCKING_SUCCESS && !is_reserved_level(new_dock.z)) - engines_cooling = TRUE - addtimer(CALLBACK(src,.proc/reset_cooldown),engine_cooldown,TIMER_UNIQUE) - -/obj/docking_port/mobile/pirate/proc/reset_cooldown() - engines_cooling = FALSE - -/obj/docking_port/mobile/pirate/canMove() - if(engines_cooling) - return FALSE - return ..() + rechargeTime = 3 MINUTES /obj/machinery/suit_storage_unit/pirate suit_type = /obj/item/clothing/suit/space helmet_type = /obj/item/clothing/head/helmet/space mask_type = /obj/item/clothing/mask/breath - storage_type = /obj/item/tank/jetpack/void + storage_type = /obj/item/tank/internals/oxygen /obj/machinery/loot_locator name = "Booty Locator" desc = "This sophisticated machine scans the nearby space for items of value." icon = 'icons/obj/machines/research.dmi' icon_state = "tdoppler" - resistance_flags = INDESTRUCTIBLE density = TRUE var/cooldown = 300 var/next_use = 0 @@ -262,13 +249,13 @@ name = "cargo hold pad" icon = 'icons/obj/telescience.dmi' icon_state = "lpad-idle-o" - resistance_flags = INDESTRUCTIBLE var/idle_state = "lpad-idle-o" var/warmup_state = "lpad-idle" var/sending_state = "lpad-beam" var/cargo_hold_id /obj/machinery/piratepad/multitool_act(mob/living/user, obj/item/multitool/I) + . = ..() if (istype(I)) to_chat(user, "You register [src] in [I]s buffer.") I.buffer = src @@ -276,8 +263,9 @@ /obj/machinery/computer/piratepad_control name = "cargo hold control terminal" - resistance_flags = INDESTRUCTIBLE - var/status_report = "Idle" + ui_x = 600 + ui_y = 230 + var/status_report = "Ready for delivery." var/obj/machinery/piratepad/pad var/warmup_time = 100 var/sending = FALSE @@ -291,10 +279,10 @@ return INITIALIZE_HINT_LATELOAD /obj/machinery/computer/piratepad_control/multitool_act(mob/living/user, obj/item/multitool/I) + . = ..() if (istype(I) && istype(I.buffer,/obj/machinery/piratepad)) to_chat(user, "You link [src] with [I.buffer] in [I] buffer.") pad = I.buffer - updateDialog() return TRUE /obj/machinery/computer/piratepad_control/LateInitialize() @@ -307,29 +295,44 @@ else pad = locate() in range(4,src) -/obj/machinery/computer/piratepad_control/ui_interact(mob/user) - . = ..() - var/list/t = list() - t += "
Cargo Hold Control
" - t += "Current cargo value : [points]" - t += "
" - if(!pad) - t += "
No pad located.

" - else - t += "
[status_report]
" - if(!sending) - t += "Recalculate ValueSend" - else - t += "Stop sending" +/obj/machinery/computer/piratepad_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "CargoHoldTerminal", name, ui_x, ui_y, master_ui, state) + ui.open() - var/datum/browser/popup = new(user, "piratepad", name, 300, 500) - popup.set_content(t.Join()) - popup.open() +/obj/machinery/computer/piratepad_control/ui_data(mob/user) + var/list/data = list() + data["points"] = points + data["pad"] = pad ? TRUE : FALSE + data["sending"] = sending + data["status_report"] = status_report + return data + +/obj/machinery/computer/piratepad_control/ui_act(action, params) + if(..()) + return + if(!pad) + return + + switch(action) + if("recalc") + recalc() + . = TRUE + if("send") + start_sending() + . = TRUE + if("stop") + stop_sending() + . = TRUE /obj/machinery/computer/piratepad_control/proc/recalc() if(sending) return - status_report = "Predicted value:
" + + status_report = "Predicted value: " + var/value = 0 var/datum/export_report/ex = new for(var/atom/movable/AM in get_turf(pad)) if(AM == pad) @@ -337,7 +340,12 @@ export_item_and_contents(AM, EXPORT_PIRATE | EXPORT_CARGO | EXPORT_CONTRABAND | EXPORT_EMAG, apply_elastic = FALSE, dry_run = TRUE, external_report = ex) for(var/datum/export/E in ex.total_amount) - status_report += E.total_printout(ex,notes = FALSE) + "
" + status_report += E.total_printout(ex,notes = FALSE) + status_report += " " + value += ex.total_value[E] + + if(!value) + status_report += "0" /obj/machinery/computer/piratepad_control/proc/send() if(!sending) @@ -350,14 +358,15 @@ continue export_item_and_contents(AM, EXPORT_PIRATE | EXPORT_CARGO | EXPORT_CONTRABAND | EXPORT_EMAG, apply_elastic = FALSE, delete_unsold = FALSE, external_report = ex) - status_report = "Sold:
" + status_report = "Sold: " var/value = 0 for(var/datum/export/E in ex.total_amount) var/export_text = E.total_printout(ex,notes = FALSE) //Don't want nanotrasen messages, makes no sense here. if(!export_text) continue - status_report += export_text + "
" + status_report += export_text + status_report += " " value += ex.total_value[E] if(!total_report) @@ -370,11 +379,13 @@ points += value + if(!value) + status_report += "Nothing" + pad.visible_message("[pad] activates!") flick(pad.sending_state,pad) pad.icon_state = pad.idle_state sending = FALSE - updateDialog() /obj/machinery/computer/piratepad_control/proc/start_sending() if(sending) @@ -389,24 +400,10 @@ if(!sending) return sending = FALSE - status_report = "Idle" + status_report = "Ready for delivery." pad.icon_state = pad.idle_state deltimer(sending_timer) -/obj/machinery/computer/piratepad_control/Topic(href, href_list) - if(..()) - return - if(pad) - if(href_list["recalc"]) - recalc() - if(href_list["send"]) - start_sending() - if(href_list["stop"]) - stop_sending() - updateDialog() - else - updateDialog() - /datum/export/pirate export_category = EXPORT_PIRATE @@ -431,6 +428,8 @@ var/mob/living/carbon/human/H = AM if(H.stat != CONSCIOUS || !H.mind || !H.mind.assigned_role) //mint condition only return 0 + else if("pirate" in H.faction) //can't ransom your fellow pirates to CentCom! + return 0 else if(H.mind.assigned_role in GLOB.command_positions) return 3000 @@ -456,3 +455,12 @@ /datum/export/pirate/cash/get_amount(obj/O) var/obj/item/stack/spacecash/C = O return ..() * C.amount * C.value + +/datum/export/pirate/holochip + cost = 1 + unit_name = "holochip" + export_types = list(/obj/item/holochip) + +/datum/export/pirate/holochip/get_cost(atom/movable/AM) + var/obj/item/holochip/H = AM + return H.credits diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm index 67a636eb9c..17cd513c6e 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm @@ -160,7 +160,7 @@ /obj/machinery/smartfridge/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "smartvend", name, 440, 550, master_ui, state) + ui = new(user, src, ui_key, "SmartVend", name, 440, 550, master_ui, state) ui.set_autoupdate(FALSE) ui.open() diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 65c69b995f..d72e1b1fc8 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -83,7 +83,7 @@ /obj/machinery/computer/holodeck/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "holodeck", name, 400, 500, master_ui, state) + ui = new(user, src, ui_key, "Holodeck", name, 400, 500, master_ui, state) ui.open() /obj/machinery/computer/holodeck/ui_data(mob/user) diff --git a/code/modules/language/language_menu.dm b/code/modules/language/language_menu.dm index eea87f1d80..d707c653e5 100644 --- a/code/modules/language/language_menu.dm +++ b/code/modules/language/language_menu.dm @@ -11,7 +11,7 @@ /datum/language_menu/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.language_menu_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "language_menu", "Language Menu", 700, 600, master_ui, state) + ui = new(user, src, ui_key, "LanguageMenu", "Language Menu", 700, 600, master_ui, state) ui.open() /datum/language_menu/ui_data(mob/user) diff --git a/code/modules/library/lib_codex_gigas.dm b/code/modules/library/lib_codex_gigas.dm index 57bf37d528..146c4221f8 100644 --- a/code/modules/library/lib_codex_gigas.dm +++ b/code/modules/library/lib_codex_gigas.dm @@ -99,7 +99,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "codex_gigas", name, 450, 450, master_ui, state) + ui = new(user, src, ui_key, "CodexGigas", name, 450, 450, master_ui, state) ui.open() /obj/item/book/codex_gigas/ui_data(mob/user) diff --git a/code/modules/library/soapstone.dm b/code/modules/library/soapstone.dm index 272e39957e..50f984c44d 100644 --- a/code/modules/library/soapstone.dm +++ b/code/modules/library/soapstone.dm @@ -209,7 +209,7 @@ /obj/structure/chisel_message/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "engraved_message", name, 600, 300, master_ui, state) + ui = new(user, src, ui_key, "EngravedMessage", name, 600, 300, master_ui, state) ui.open() /obj/structure/chisel_message/ui_data(mob/user) diff --git a/code/modules/mining/laborcamp/laborstacker.dm b/code/modules/mining/laborcamp/laborstacker.dm index 14a277a66c..44aa3ebec7 100644 --- a/code/modules/mining/laborcamp/laborstacker.dm +++ b/code/modules/mining/laborcamp/laborstacker.dm @@ -8,6 +8,9 @@ GLOBAL_LIST(labor_sheet_values) icon = 'icons/obj/machines/mining_machines.dmi' icon_state = "console" density = FALSE + ui_x = 315 + ui_y = 430 + var/obj/machinery/mineral/stacking_machine/laborstacker/stacking_machine = null var/machinedir = SOUTH var/obj/machinery/door/airlock/release_door @@ -36,7 +39,7 @@ GLOBAL_LIST(labor_sheet_values) datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "labor_claim_console", name, 315, 430, master_ui, state) + ui = new(user, src, ui_key, "LaborClaimConsole", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/mineral/labor_claim_console/ui_data(mob/user) @@ -100,7 +103,6 @@ GLOBAL_LIST(labor_sheet_values) Radio.talk_into(src, "A prisoner has returned to the station. Minerals and Prisoner ID card ready for retrieval.", FREQ_SECURITY) to_chat(usr, "Shuttle received message and will be sent shortly.") . = TRUE - /obj/machinery/mineral/labor_claim_console/proc/locate_stacking_machine() stacking_machine = locate(/obj/machinery/mineral/stacking_machine, get_step(src, machinedir)) @@ -110,19 +112,16 @@ GLOBAL_LIST(labor_sheet_values) qdel(src) /obj/machinery/mineral/labor_claim_console/emag_act(mob/user) - . = ..() - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - to_chat(user, "PZZTTPFFFT") - return TRUE + if(!(obj_flags & EMAGGED)) + obj_flags |= EMAGGED + to_chat(user, "PZZTTPFFFT") /**********************Prisoner Collection Unit**************************/ /obj/machinery/mineral/stacking_machine/laborstacker force_connect = TRUE var/points = 0 //The unclaimed value of ore stacked. - + damage_deflection = 21 /obj/machinery/mineral/stacking_machine/laborstacker/process_sheet(obj/item/stack/sheet/inp) points += inp.point_value * inp.amount ..() diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 2f6e8a9b7c..d4703b8d94 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -193,7 +193,7 @@ /obj/machinery/mineral/ore_redemption/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "ore_redemption_machine", "Ore Redemption Machine", 440, 550, master_ui, state) + ui = new(user, src, ui_key, "OreRedemptionMachine", "Ore Redemption Machine", 440, 550, master_ui, state) ui.open() /obj/machinery/mineral/ore_redemption/ui_data(mob/user) diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm index 0182dec254..909807b9b1 100644 --- a/code/modules/mining/machine_vending.dm +++ b/code/modules/mining/machine_vending.dm @@ -93,10 +93,22 @@ else icon_state = "[initial(icon_state)]-off" -/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user) - . = ..() - var/list/dat = list() - dat += "
Equipment point cost list:
" +/obj/machinery/mineral/equipment_vendor/ui_base_html(html) + var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/vending) + . = replacetext(html, "", assets.css_tag()) + +/obj/machinery/mineral/equipment_vendor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/vending) + assets.send(user) + ui = new(user, src, ui_key, "MiningVendor", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/mineral/equipment_vendor/ui_static_data(mob/user) + . = list() + .["product_records"] = list() for(var/datum/data/mining_equipment/prize in prize_list) dat += "" dat += "
[prize.equipment_name][prize.cost]Purchase
" diff --git a/code/modules/mining/mint.dm b/code/modules/mining/mint.dm index d04c0104e5..81a9f873e4 100644 --- a/code/modules/mining/mint.dm +++ b/code/modules/mining/mint.dm @@ -6,11 +6,14 @@ icon = 'icons/obj/economy.dmi' icon_state = "coinpress0" density = TRUE - var/newCoins = 0 //how many coins the machine made in it's last load + input_dir = EAST + ui_x = 300 + ui_y = 250 + needs_item_input = TRUE + + var/produced_coins = 0 // how many coins the machine has made in it's last cycle var/processing = FALSE var/chosen = /datum/material/iron //which material will be used to make coins - var/coinsToProduce = 10 - speed_process = TRUE /obj/machinery/mineral/mint/Initialize() @@ -28,89 +31,102 @@ /datum/material/mythril, /datum/material/plastic, /datum/material/runite - ), MINERAL_MATERIAL_AMOUNT * 50, FALSE, /obj/item/stack) - chosen = SSmaterials.GetMaterialRef(chosen) + ), MINERAL_MATERIAL_AMOUNT * 75, FALSE, /obj/item/stack) + chosen = SSmaterials.GetMaterialRef(chosen) -/obj/machinery/mineral/mint/process() - var/turf/T = get_step(src, input_dir) - if(!T) + +/obj/machinery/mineral/mint/pickup_item(datum/source, atom/movable/target, atom/oldLoc) + if(!istype(target, /obj/item/stack)) return var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/obj/item/stack/sheet/O in T) - var/inserted = materials.insert_item(O) - if(inserted) - qdel(O) + var/obj/item/stack/S = target -/obj/machinery/mineral/mint/attack_hand(mob/user) + if(materials.insert_item(S)) + qdel(S) + +/obj/machinery/mineral/mint/process() + if(processing) + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + var/datum/material/M = chosen + + if(!M) + processing = FALSE + icon_state = "coinpress0" + return + + icon_state = "coinpress1" + var/coin_mat = MINERAL_MATERIAL_AMOUNT + + for(var/sheets in 1 to 2) + if(materials.use_amount_mat(coin_mat, chosen)) + for(var/coin_to_make in 1 to 5) + create_coins() + produced_coins++ + else + var/found_new = FALSE + for(var/datum/material/inserted_material in materials.materials) + var/amount = materials.get_material_amount(inserted_material) + + if(amount) + chosen = inserted_material + found_new = TRUE + + if(!found_new) + processing = FALSE + else + end_processing() + icon_state = "coinpress0" + +/obj/machinery/mineral/mint/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "Mint", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/mineral/mint/ui_data() + var/list/data = list() + data["inserted_materials"] = list() + data["chosen_material"] = null + + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/inserted_material in materials.materials) + var/amount = materials.get_material_amount(inserted_material) + if(!amount) + continue + data["inserted_materials"] += list(list( + "material" = inserted_material.name, + "amount" = amount, + )) + if(chosen == inserted_material) + data["chosen_material"] = inserted_material.name + + data["produced_coins"] = produced_coins + data["processing"] = processing + + return data; + +/obj/machinery/mineral/mint/ui_act(action, params, datum/tgui/ui) . = ..() if(.) return - var/dat = "Coin Press
" - - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - for(var/datum/material/M in materials.materials) - var/amount = materials.get_material_amount(M) - if(!amount && chosen != M) - continue - dat += "
[M.name] amount: [amount] cm3 " - if (chosen == M) - dat += "Chosen" - else - dat += "Choose" - - var/datum/material/M = chosen - - dat += "

Will produce [coinsToProduce] [lowertext(M.name)] coins if enough materials are available.
" - dat += "-10 " - dat += "-5 " - dat += "-1 " - dat += "+1 " - dat += "+5 " - dat += "+10 " - - dat += "

In total this machine produced [newCoins] coins." - dat += "
Make coins" - user << browse(dat, "window=mint") - -/obj/machinery/mineral/mint/Topic(href, href_list) - if(..()) - return - usr.set_machine(src) - src.add_fingerprint(usr) - if(processing==1) - to_chat(usr, "The machine is processing.") - return - var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - if(href_list["choose"]) - var/datum/material/new_material = locate(href_list["choose"]) - if(istype(new_material)) - chosen = new_material - if(href_list["chooseAmt"]) - coinsToProduce = CLAMP(coinsToProduce + text2num(href_list["chooseAmt"]), 0, 1000) - updateUsrDialog() - if(href_list["makeCoins"]) - var/temp_coins = coinsToProduce + if(action == "startpress") + if (!processing) + produced_coins = 0 processing = TRUE - icon_state = "coinpress1" - var/coin_mat = MINERAL_MATERIAL_AMOUNT * 0.2 - var/datum/material/M = chosen - if(!M) - updateUsrDialog() - return - - while(coinsToProduce > 0 && materials.use_amount_mat(coin_mat, chosen)) - create_coins() - coinsToProduce-- - newCoins++ - src.updateUsrDialog() - sleep(5) - - icon_state = "coinpress0" + begin_processing() + return TRUE + if (action == "stoppress") processing = FALSE - coinsToProduce = temp_coins - src.updateUsrDialog() - return + end_processing() + return TRUE + if (action == "changematerial") + var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) + for(var/datum/material/mat in materials.materials) + if (params["material_name"] == mat.name) + chosen = mat + return TRUE /obj/machinery/mineral/mint/proc/create_coins() var/turf/T = get_step(src,output_dir) diff --git a/code/modules/mining/satchel_ore_boxdm.dm b/code/modules/mining/satchel_ore_boxdm.dm index 1d803371be..e689a37b25 100644 --- a/code/modules/mining/satchel_ore_boxdm.dm +++ b/code/modules/mining/satchel_ore_boxdm.dm @@ -9,6 +9,9 @@ density = TRUE pressure_resistance = 5*ONE_ATMOSPHERE + var/ui_x = 335 + var/ui_y = 415 + /obj/structure/ore_box/attackby(obj/item/W, mob/user, params) if (istype(W, /obj/item/stack/ore)) user.transferItemToLoc(W, src) @@ -24,16 +27,16 @@ /obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I) if(I.use_tool(src, user, 50, volume=50)) - user.visible_message("[user] pries \the [src] apart.", + user.visible_message("[user] pries \the [src] apart.", "You pry apart \the [src].", - "You hear splitting wood.") + "You hear splitting wood.") deconstruct(TRUE, user) return TRUE /obj/structure/ore_box/examine(mob/living/user) if(Adjacent(user) && istype(user)) ui_interact(user) - return ..() + . = ..() /obj/structure/ore_box/attack_hand(mob/user) . = ..() @@ -62,7 +65,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "ore_box", name, 335, 415, master_ui, state) + ui = new(user, src, ui_key, "OreBox", name, ui_x, ui_y, master_ui, state) ui.open() /obj/structure/ore_box/ui_data() diff --git a/code/modules/mob/dead/observer/notificationprefs.dm b/code/modules/mob/dead/observer/notificationprefs.dm index 6c1d76eaf3..160abd57e1 100644 --- a/code/modules/mob/dead/observer/notificationprefs.dm +++ b/code/modules/mob/dead/observer/notificationprefs.dm @@ -24,7 +24,7 @@ /datum/notificationpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "notificationpanel", "Notification Preferences", 270, 360, master_ui, state) + ui = new(user, src, ui_key, "NotificationPreferences", "Notification Preferences", 270, 360, master_ui, state) ui.open() /datum/notificationpanel/ui_data(mob/user) diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 775f36cfc7..854182bd99 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -302,7 +302,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "slime_swap_body", name, 400, 400, master_ui, state) + ui = new(user, src, ui_key, "SlimeBodySwapper", name, 400, 400, master_ui, state) ui.open() /datum/action/innate/swap_body/ui_data(mob/user) diff --git a/code/modules/mob/living/silicon/ai/robot_control.dm b/code/modules/mob/living/silicon/ai/robot_control.dm new file mode 100644 index 0000000000..0eaea103f2 --- /dev/null +++ b/code/modules/mob/living/silicon/ai/robot_control.dm @@ -0,0 +1,74 @@ +/datum/robot_control + var/mob/living/silicon/ai/owner + +/datum/robot_control/New(mob/living/silicon/ai/new_owner) + if(!istype(new_owner)) + qdel(src) + owner = new_owner + +/datum/robot_control/proc/is_interactable(mob/user) + if(user != owner || owner.incapacitated()) + return FALSE + if(owner.control_disabled) + to_chat(user, "Wireless control is disabled.") + return FALSE + return TRUE + +/datum/robot_control/ui_status(mob/user) + if(is_interactable(user)) + return ..() + return UI_CLOSE + +/datum/robot_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "RemoteRobotControl", "Remote Robot Control", 500, 500, master_ui, state) + ui.open() + +/datum/robot_control/ui_data(mob/user) + if(!owner || user != owner) + return + var/list/data = list() + var/turf/ai_current_turf = get_turf(owner) + var/ai_zlevel = ai_current_turf.z + + data["robots"] = list() + for(var/mob/living/simple_animal/bot/B in GLOB.bots_list) + if(B.z != ai_zlevel || B.remote_disabled) //Only non-emagged bots on the same Z-level are detected! + continue + var/list/robot_data = list( + name = B.name, + model = B.model, + mode = B.get_mode(), + hacked = B.hacked, + location = get_area_name(B, TRUE), + ref = REF(B) + ) + data["robots"] += list(robot_data) + + return data + +/datum/robot_control/ui_act(action, params) + if(..()) + return + if(!is_interactable(usr)) + return + + switch(action) + if("callbot") //Command a bot to move to a selected location. + if(owner.call_bot_cooldown > world.time) + to_chat(usr, "Error: Your last call bot command is still processing, please wait for the bot to finish calculating a route.") + return + owner.Bot = locate(params["ref"]) in GLOB.bots_list + if(!owner.Bot || owner.Bot.remote_disabled || owner.control_disabled) + return + owner.waypoint_mode = TRUE + to_chat(usr, "Set your waypoint by clicking on a valid location free of obstructions.") + . = TRUE + if("interface") //Remotely connect to a bot! + owner.Bot = locate(params["ref"]) in GLOB.bots_list + if(!owner.Bot || owner.Bot.remote_disabled || owner.control_disabled) + return + owner.Bot.attack_ai(usr) + . = TRUE diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 917fdcf113..6721a504c4 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -174,7 +174,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "mulebot", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "Mule", name, ui_x, ui_y, master_ui, state) ui.open() /mob/living/simple_animal/bot/mulebot/ui_data(mob/user) diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index 11f5145478..6266daad2b 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -37,9 +37,9 @@ if (!ui) var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) assets.send(user) - - ui = new(user, src, ui_key, "ntos_main", "NtOS Main menu", 400, 500, master_ui, state) - ui.set_style("ntos") + assets = get_asset_datum(/datum/asset/simple/arcade) + assets.send(user) + ui = new(user, src, ui_key, "NtosMain", "NtOS Main menu", 400, 500, master_ui, state) ui.open() ui.set_autoupdate(state = 1) diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm index 1aa292f247..8b9f98ca73 100644 --- a/code/modules/modular_computers/file_system/programs/airestorer.dm +++ b/code/modules/modular_computers/file_system/programs/airestorer.dm @@ -7,9 +7,9 @@ requires_ntnet = 0 usage_flags = PROGRAM_CONSOLE transfer_access = ACCESS_HEADS - available_on_ntnet = 1 - tgui_id = "ntos_ai_restorer" - ui_x = 600 + available_on_ntnet = TRUE + tgui_id = "NtosAiRestorer" + ui_x = 370 ui_y = 400 var/restoring = FALSE @@ -118,4 +118,4 @@ /datum/computer_file/program/aidiag/kill_program(forced) restoring = FALSE - return ..(forced) \ No newline at end of file + return ..(forced) diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index ca075b51e4..ebfd3c0ad8 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -7,7 +7,7 @@ requires_ntnet = 1 network_destination = "alarm monitoring network" size = 5 - tgui_id = "ntos_station_alert" + tgui_id = "NtosStationAlertConsole" ui_x = 315 ui_y = 500 diff --git a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm index 35470cdee9..326f885ee7 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm @@ -8,9 +8,8 @@ available_on_ntnet = 0 unsendable = 1 undeletable = 1 - tgui_id = "synd_contract" - ui_style = "syndicate" - ui_x = 600 + tgui_id = "SyndContractor" + ui_x = 500 ui_y = 600 var/error = "" var/page = CONTRACT_UPLINK_PAGE_CONTRACTS @@ -165,4 +164,4 @@ screen_to_be = "assign" program_icon_state = screen_to_be update_computer_icon() - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm index 337e98acaa..a2bc2df49b 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -4,11 +4,10 @@ program_icon_state = "hostile" extended_desc = "This advanced script can perform denial of service attacks against NTNet quantum relays. The system administrator will probably notice this. Multiple devices can run this program together against same relay for increased effect" size = 20 - requires_ntnet = 1 - available_on_ntnet = 0 - available_on_syndinet = 1 - tgui_id = "ntos_net_dos" - ui_style = "syndicate" + requires_ntnet = TRUE + available_on_ntnet = FALSE + available_on_syndinet = TRUE + tgui_id = "NtosNetDos" ui_x = 400 ui_y = 250 @@ -97,4 +96,4 @@ data["relays"] += list(list("id" = R.uid)) data["focus"] = target ? target.uid : null - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm index 103b70e496..5b8f6a0ae3 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -4,11 +4,10 @@ program_icon_state = "hostile" extended_desc = "This virus can destroy hard drive of system it is executed on. It may be obfuscated to look like another non-malicious program. Once armed, it will destroy the system upon next execution." size = 13 - requires_ntnet = 0 - available_on_ntnet = 0 - available_on_syndinet = 1 - tgui_id = "ntos_revelation" - ui_style = "syndicate" + requires_ntnet = FALSE + available_on_ntnet = FALSE + available_on_syndinet = TRUE + tgui_id = "NtosRevelation" ui_x = 400 ui_y = 250 @@ -68,4 +67,4 @@ data["armed"] = armed - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/arcade.dm b/code/modules/modular_computers/file_system/programs/arcade.dm new file mode 100644 index 0000000000..efd82a9d88 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/arcade.dm @@ -0,0 +1,176 @@ +/datum/computer_file/program/arcade + filename = "arcade" + filedesc = "Nanotrasen Micro Arcade" + program_icon_state = "arcade" + extended_desc = "This port of the classic game 'Outbomb Cuban Pete', redesigned to run on tablets, with thrilling graphics and chilling storytelling." + requires_ntnet = FALSE + network_destination = "arcade network" + size = 6 + tgui_id = "NtosArcade" + ui_x = 450 + ui_y = 350 + + ///Returns TRUE if the game is being played. + var/game_active = TRUE + ///This disables buttom actions from having any impact if TRUE. Resets to FALSE when the player is allowed to make an action again. + var/pause_state = FALSE + var/boss_hp = 45 + var/boss_mp = 15 + var/player_hp = 30 + var/player_mp = 10 + var/ticket_count = 0 + ///Shows what text is shown on the app, usually showing the log of combat actions taken by the player. + var/heads_up = "Nanotrasen says, winners make us money." + var/boss_name = "Cuban Pete's Minion" + ///Determines which boss image to use on the UI. + var/boss_id = 1 + +/datum/computer_file/program/arcade/proc/game_check(mob/user) + sleep(5) + user?.mind?.adjust_experience(/datum/skill/gaming, 1) + if(boss_hp <= 0) + heads_up = "You have crushed [boss_name]! Rejoice!" + playsound(computer.loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3, falloff = 10) + game_active = FALSE + program_icon_state = "arcade_off" + if(istype(computer)) + computer.update_icon() + ticket_count += 1 + user?.mind?.adjust_experience(/datum/skill/gaming, 50) + sleep(10) + else if(player_hp <= 0 || player_mp <= 0) + heads_up = "You have been defeated... how will the station survive?" + playsound(computer.loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3, falloff = 10) + game_active = FALSE + program_icon_state = "arcade_off" + if(istype(computer)) + computer.update_icon() + user?.mind?.adjust_experience(/datum/skill/gaming, 10) + sleep(10) + +/datum/computer_file/program/arcade/proc/enemy_check(mob/user) + var/boss_attackamt = 0 //Spam protection from boss attacks as well. + var/boss_mpamt = 0 + var/bossheal = 0 + if(pause_state == TRUE) + boss_attackamt = rand(3,6) + boss_mpamt = rand (2,4) + bossheal = rand (4,6) + if(game_active == FALSE) + return + if (boss_mp <= 5) + heads_up = "[boss_mpamt] magic power has been stolen from you!" + playsound(computer.loc, 'sound/arcade/steal.ogg', 50, TRUE, extrarange = -3, falloff = 10) + player_mp -= boss_mpamt + boss_mp += boss_mpamt + else if(boss_mp > 5 && boss_hp <12) + heads_up = "[boss_name] heals for [bossheal] health!" + playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3, falloff = 10) + boss_hp += bossheal + boss_mp -= boss_mpamt + else + heads_up = "[boss_name] attacks you for [boss_attackamt] damage!" + playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10) + player_hp -= boss_attackamt + + pause_state = FALSE + game_check() + +/datum/computer_file/program/arcade/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/ui_state/state) + . = ..() + var/datum/asset/assets = get_asset_datum(/datum/asset/simple/arcade) + assets.send(user) + +/datum/computer_file/program/arcade/ui_data(mob/user) + var/list/data = get_header_data() + + data["Hitpoints"] = boss_hp + data["PlayerHitpoints"] = player_hp + data["PlayerMP"] = player_mp + data["TicketCount"] = ticket_count + data["GameActive"] = game_active + data["PauseState"] = pause_state + data["Status"] = heads_up + data["BossID"] = "boss[boss_id].gif" + return data + +/datum/computer_file/program/arcade/ui_act(action, list/params) + if(..()) + return TRUE + var/obj/item/computer_hardware/printer/printer + if(computer) + printer = computer.all_components[MC_PRINT] + + var/gamerSkillLevel = usr.mind?.get_skill_level(/datum/skill/gaming) + var/gamerSkill = usr.mind?.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER) + switch(action) + if("Attack") + var/attackamt = 0 //Spam prevention. + if(pause_state == FALSE) + attackamt = rand(2,6) + rand(0, gamerSkill) + pause_state = TRUE + heads_up = "You attack for [attackamt] damage." + playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10) + boss_hp -= attackamt + sleep(10) + game_check() + enemy_check() + return TRUE + if("Heal") + var/healamt = 0 //More Spam Prevention. + var/healcost = 0 + if(pause_state == FALSE) + healamt = rand(6,8) + rand(0, gamerSkill) + var/maxPointCost = 3 + if(gamerSkillLevel >= SKILL_LEVEL_JOURNEYMAN) + maxPointCost = 2 + healcost = rand(1, maxPointCost) + pause_state = TRUE + heads_up = "You heal for [healamt] damage." + playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3, falloff = 10) + player_hp += healamt + player_mp -= healcost + sleep(10) + game_check() + enemy_check() + return TRUE + if("Recharge_Power") + var/rechargeamt = 0 //As above. + if(pause_state == FALSE) + rechargeamt = rand(4,7) + rand(0, gamerSkill) + pause_state = TRUE + heads_up = "You regain [rechargeamt] magic power." + playsound(computer.loc, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3, falloff = 10) + player_mp += rechargeamt + sleep(10) + game_check() + enemy_check() + return TRUE + if("Dispense_Tickets") + if(!printer) + to_chat(usr, "Hardware error: A printer is required to redeem tickets.") + return + if(printer.stored_paper <= 0) + to_chat(usr, "Hardware error: Printer is out of paper.") + return + else + computer.visible_message("\The [computer] prints out paper.") + if(ticket_count >= 1) + new /obj/item/stack/arcadeticket((get_turf(computer)), 1) + to_chat(usr, "[src] dispenses a ticket!") + ticket_count -= 1 + printer.stored_paper -= 1 + else + to_chat(usr, "You don't have any stored tickets!") + return TRUE + if("Start_Game") + game_active = TRUE + boss_hp = 45 + player_hp = 30 + player_mp = 10 + heads_up = "You stand before [boss_name]! Prepare for battle!" + program_icon_state = "arcade" + boss_id = rand(1,6) + pause_state = FALSE + if(istype(computer)) + computer.update_icon() diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm new file mode 100644 index 0000000000..fe3833facd --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm @@ -0,0 +1,33 @@ +/datum/computer_file/program/atmosscan + filename = "atmosscan" + filedesc = "Atmospheric Scanner" + program_icon_state = "air" + extended_desc = "A small built-in sensor reads out the atmospheric conditions around the device." + network_destination = "atmos scan" + size = 4 + tgui_id = "NtosAtmos" + ui_x = 300 + ui_y = 350 + +/datum/computer_file/program/atmosscan/ui_data(mob/user) + var/list/data = get_header_data() + var/list/airlist = list() + var/turf/T = get_turf(ui_host()) + if(T) + var/datum/gas_mixture/environment = T.return_air() + var/list/env_gases = environment.gases + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + data["AirPressure"] = round(pressure,0.1) + data["AirTemp"] = round(environment.temperature-T0C) + if (total_moles) + for(var/id in env_gases) + var/gas_level = env_gases[id][MOLES]/total_moles + if(gas_level > 0) + airlist += list(list("name" = "[env_gases[id][GAS_META][META_GAS_NAME]]", "percentage" = round(gas_level*100, 0.01))) + data["AirData"] = airlist + return data + +/datum/computer_file/program/atmosscan/ui_act(action, list/params) + if(..()) + return TRUE diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm new file mode 100644 index 0000000000..e3e43dcf84 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm @@ -0,0 +1,69 @@ +/datum/computer_file/program/borg_monitor + filename = "cyborgmonitor" + filedesc = "Cyborg Remote Monitoring" + ui_header = "borg_mon.gif" + program_icon_state = "generic" + extended_desc = "This program allows for remote monitoring of station cyborgs." + requires_ntnet = TRUE + transfer_access = ACCESS_ROBOTICS + network_destination = "cyborg remote monitoring" + size = 5 + tgui_id = "NtosCyborgRemoteMonitor" + ui_x = 600 + ui_y = 800 + +/datum/computer_file/program/borg_monitor/ui_data(mob/user) + var/list/data = get_header_data() + + data["card"] = FALSE + if(computer.GetID()) + data["card"] = TRUE + + data["cyborgs"] = list() + for(var/mob/living/silicon/robot/R in GLOB.silicon_mobs) + if((get_turf(computer)).z != (get_turf(R)).z) + continue + if(R.scrambledcodes) + continue + + var/list/upgrade + for(var/obj/item/borg/upgrade/I in R.upgrades) + upgrade += "\[[I.name]\] " + + var/shell = FALSE + if(R.shell && !R.ckey) + shell = TRUE + + var/list/cyborg_data = list( + name = R.name, + locked_down = R.lockcharge, + status = R.stat, + shell_discon = shell, + charge = R.cell ? round(R.cell.percent()) : null, + module = R.module ? "[R.module.name] Module" : "No Module Detected", + upgrades = upgrade, + ref = REF(R) + ) + data["cyborgs"] += list(cyborg_data) + return data + +/datum/computer_file/program/borg_monitor/ui_act(action, params) + if(..()) + return + + switch(action) + if("messagebot") + var/mob/living/silicon/robot/R = locate(params["ref"]) in GLOB.silicon_mobs + if(!istype(R)) + return + var/obj/item/card/id/ID = computer.GetID() + if(!ID) + return + var/message = stripped_input(usr, message = "Enter message to be sent to remote cyborg.", title = "Send Message") + if(!message) + return + to_chat(R, "

Message from [ID.registered_name] -- \"[message]\"
") + SEND_SOUND(R, 'sound/machines/twobeep_high.ogg') + if(R.connected_ai) + to_chat(R.connected_ai, "

Message from [ID.registered_name] to [R] -- \"[message]\"
") + SEND_SOUND(R.connected_ai, 'sound/machines/twobeep_high.ogg') diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm index bf58b120f2..6309a1a89f 100644 --- a/code/modules/modular_computers/file_system/programs/card.dm +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -6,91 +6,84 @@ transfer_access = ACCESS_HEADS requires_ntnet = 0 size = 8 - tgui_id = "ntos_card" - ui_x = 600 - ui_y = 700 + tgui_id = "NtosCard" + ui_x = 450 + ui_y = 520 - var/mod_mode = 1 - var/is_centcom = 0 - var/show_assignments = 0 - var/minor = 0 - var/authenticated = 0 - var/list/reg_ids = list() - var/list/region_access = null - var/list/head_subordinates = null - var/target_dept = 0 //Which department this computer has access to. 0=all departments - var/change_position_cooldown = 30 - //Jobs you cannot open new positions for - var/list/blacklisted = list( - "AI", - "Assistant", - "Cyborg", - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer") + var/is_centcom = FALSE + var/minor = FALSE + var/authenticated = FALSE + var/list/region_access + var/list/head_subordinates + ///Which departments this computer has access to. Defined as access regions. null = all departments + var/target_dept - //The scaling factor of max total positions in relation to the total amount of people on board the station in % - var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players + //For some reason everything was exploding if this was static. + var/list/sub_managers - //This is used to keep track of opened positions for jobs to allow instant closing - //Assoc array: "JobName" = (int) - var/list/opened_positions = list(); +/datum/computer_file/program/card_mod/New(obj/item/modular_computer/comp) + . = ..() + sub_managers = list( + "[ACCESS_HOP]" = list( + "department" = list(CARDCON_DEPARTMENT_SERVICE, CARDCON_DEPARTMENT_COMMAND), + "region" = 1, + "head" = "Head of Personnel" + ), + "[ACCESS_HOS]" = list( + "department" = CARDCON_DEPARTMENT_SECURITY, + "region" = 2, + "head" = "Head of Security" + ), + "[ACCESS_CMO]" = list( + "department" = CARDCON_DEPARTMENT_MEDICAL, + "region" = 3, + "head" = "Chief Medical Officer" + ), + "[ACCESS_RD]" = list( + "department" = CARDCON_DEPARTMENT_SCIENCE, + "region" = 4, + "head" = "Research Director" + ), + "[ACCESS_CE]" = list( + "department" = CARDCON_DEPARTMENT_ENGINEERING, + "region" = 5, + "head" = "Chief Engineer" + ) + ) -/datum/computer_file/program/card_mod/New() - ..() - addtimer(CALLBACK(src, .proc/SetConfigCooldown), 0) +/datum/computer_file/program/card_mod/proc/authenticate(mob/user, obj/item/card/id/id_card) + if(!id_card) + return -/datum/computer_file/program/card_mod/proc/SetConfigCooldown() - change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) + region_access = list() + if(!target_dept && (ACCESS_CHANGE_IDS in id_card.access)) + minor = FALSE + authenticated = TRUE + update_static_data(user) + return TRUE -/datum/computer_file/program/card_mod/event_idremoved(background, slot) - if(!slot || slot == 2)// slot being false means both are removed - minor = 0 - authenticated = 0 - head_subordinates = null - region_access = null + var/list/head_types = list() + for(var/access_text in sub_managers) + var/list/info = sub_managers[access_text] + var/access = text2num(access_text) + if((access in id_card.access) && ((info["region"] in target_dept) || !length(target_dept))) + region_access += info["region"] + //I don't even know what I'm doing anymore + head_types += info["head"] + head_subordinates = list() + if(length(head_types)) + for(var/j in SSjob.occupations) + var/datum/job/job = j + for(var/head in head_types)//god why + if(head in job.department_head) + head_subordinates += job.title -/datum/computer_file/program/card_mod/proc/job_blacklisted(jobtitle) - return (jobtitle in blacklisted) - - -//Logic check for if you can open the job -/datum/computer_file/program/card_mod/proc/can_open_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) - return 1 - return -2 - return 0 - return 0 - -//Logic check for if you can close the job -/datum/computer_file/program/card_mod/proc/can_close_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if(job.total_positions > job.current_positions) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) - return 1 - return -2 - return 0 - return 0 - -/datum/computer_file/program/card_mod/proc/format_jobs(list/jobs) - var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] - var/obj/item/card/id/id_card = card_slot.stored_card - var/list/formatted = list() - for(var/job in jobs) - formatted.Add(list(list( - "display_name" = replacetext(job, " ", " "), - "target_rank" = id_card && id_card.assignment ? id_card.assignment : "Unassigned", - "job" = job))) + if(length(region_access)) + minor = TRUE + authenticated = TRUE + update_static_data(user) + return TRUE return formatted diff --git a/code/modules/modular_computers/file_system/programs/cargoship.dm b/code/modules/modular_computers/file_system/programs/cargoship.dm new file mode 100644 index 0000000000..39543adfa5 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/cargoship.dm @@ -0,0 +1,74 @@ +/datum/computer_file/program/shipping + filename = "shipping" + filedesc = "Nanotrasen Shipping Scanner" + program_icon_state = "shipping" + extended_desc = "A combination printer/scanner app that enables modular computers to print barcodes for easy scanning and shipping." + network_destination = "ship scanner" + size = 6 + tgui_id = "NtosShipping" + ui_x = 450 + ui_y = 350 + ///Account used for creating barcodes. + var/datum/bank_account/payments_acc + ///The amount which the tagger will recieve for the sale. + var/percent_cut = 20 + +/datum/computer_file/program/shipping/ui_data(mob/user) + var/list/data = get_header_data() + + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + var/obj/item/computer_hardware/printer/printer = computer.all_components[MC_PRINT] + var/obj/item/card/id/id_card = card_slot ? card_slot.stored_card : null + data["has_id_slot"] = !!card_slot + data["has_printer"] = !!printer + data["paperamt"] = printer ? "[printer.stored_paper] / [printer.max_paper]" : null + data["card_owner"] = card_slot && card_slot.stored_card ? id_card.registered_name : "No Card Inserted." + data["current_user"] = payments_acc ? payments_acc.account_holder : null + data["barcode_split"] = percent_cut + return data + +/datum/computer_file/program/shipping/ui_act(action, list/params) + if(..()) + return TRUE + if(!computer) + return + + // Get components + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + var/obj/item/computer_hardware/printer/printer = computer.all_components[MC_PRINT] + var/obj/item/card/id/id_card = card_slot ? card_slot.stored_card : null + if(!card_slot || !printer) //We need both to successfully use this app. + return + + switch(action) + if("ejectid") + if(id_card) + card_slot.try_eject(TRUE, usr) + if("selectid") + if(!id_card) + return + if(!id_card.registered_account) + playsound(get_turf(ui_host()), 'sound/machines/buzz-sigh.ogg', 50, TRUE, -1) + return + payments_acc = id_card.registered_account + playsound(get_turf(ui_host()), 'sound/machines/ping.ogg', 50, TRUE, -1) + if("resetid") + payments_acc = null + if("setsplit") + var/potential_cut = input("How much would you like to payout to the registered card?","Percentage Profit") as num|null + percent_cut = potential_cut ? clamp(round(potential_cut, 1), 1, 50) : 20 + if("print") + if(!printer) + to_chat(usr, "Hardware error: A printer is required to print barcodes.") + return + if(printer.stored_paper <= 0) + to_chat(usr, "Hardware error: Printer is out of paper.") + return + if(!payments_acc) + to_chat(usr, "Software error: Please set a current user first.") + return + var/obj/item/barcode/barcode = new /obj/item/barcode(get_turf(ui_host())) + barcode.payments_acc = payments_acc + barcode.percent_cut = percent_cut + printer.stored_paper-- + to_chat(usr, "The computer prints out a barcode.") diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/configurator.dm index 2d60323d10..76da58ea11 100644 --- a/code/modules/modular_computers/file_system/programs/configurator.dm +++ b/code/modules/modular_computers/file_system/programs/configurator.dm @@ -14,7 +14,7 @@ ui_y = 630 available_on_ntnet = 0 requires_ntnet = 0 - tgui_id = "ntos_configuration" + tgui_id = "NtosConfiguration" var/obj/item/modular_computer/movable = null diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm new file mode 100644 index 0000000000..662c867a39 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm @@ -0,0 +1,50 @@ +/datum/computer_file/program/crew_manifest + filename = "crewmani" + filedesc = "Crew Manifest" + program_icon_state = "id" + extended_desc = "Program for viewing and printing the current crew manifest" + transfer_access = ACCESS_HEADS + requires_ntnet = FALSE + size = 4 + tgui_id = "NtosCrewManifest" + ui_x = 400 + ui_y = 480 + +/datum/computer_file/program/crew_manifest/ui_static_data(mob/user) + var/list/data = list() + data["manifest"] = GLOB.data_core.get_manifest() + return data + +/datum/computer_file/program/crew_manifest/ui_data(mob/user) + var/list/data = get_header_data() + + var/obj/item/computer_hardware/printer/printer + if(computer) + printer = computer.all_components[MC_PRINT] + + if(computer) + data["have_printer"] = !!printer + else + data["have_printer"] = FALSE + return data + +/datum/computer_file/program/crew_manifest/ui_act(action, params, datum/tgui/ui) + if(..()) + return + + var/obj/item/computer_hardware/printer/printer + if(computer) + printer = computer.all_components[MC_PRINT] + + switch(action) + if("PRG_print") + if(computer && printer) //This option should never be called if there is no printer + var/contents = {"

Crew Manifest

+
+ [GLOB.data_core ? GLOB.data_core.get_manifest_html(0) : ""] + "} + if(!printer.print_text(contents,text("crew manifest ([])", station_time_timestamp()))) + to_chat(usr, "Hardware error: Printer was unable to print the file. It may be out of paper.") + return + else + computer.visible_message("\The [computer] prints out a paper.") diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm index cba82eac18..fd3b81be25 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -4,10 +4,10 @@ extended_desc = "This program allows management of files." program_icon_state = "generic" size = 8 - requires_ntnet = 0 - available_on_ntnet = 0 - undeletable = 1 - tgui_id = "ntos_file_manager" + requires_ntnet = FALSE + available_on_ntnet = FALSE + undeletable = TRUE + tgui_id = "NtosFileManager" var/open_file var/error diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm new file mode 100644 index 0000000000..7b847c123e --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm @@ -0,0 +1,141 @@ +/datum/computer_file/program/job_management + filename = "job_manage" + filedesc = "Job Manager" + program_icon_state = "id" + extended_desc = "Program for viewing and changing job slot avalibility." + transfer_access = ACCESS_HEADS + requires_ntnet = 0 + size = 4 + tgui_id = "NtosJobManager" + ui_x = 400 + ui_y = 620 + + var/change_position_cooldown = 30 + //Jobs you cannot open new positions for + var/list/blacklisted = list( + "AI", + "Assistant", + "Cyborg", + "Captain", + "Head of Personnel", + "Head of Security", + "Chief Engineer", + "Research Director", + "Chief Medical Officer") + + //The scaling factor of max total positions in relation to the total amount of people on board the station in % + var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players + + //This is used to keep track of opened positions for jobs to allow instant closing + //Assoc array: "JobName" = (int) + var/list/opened_positions = list() + +/datum/computer_file/program/job_management/New() + ..() + change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) + +/datum/computer_file/program/job_management/proc/can_open_job(datum/job/job) + if(!(job?.title in blacklisted)) + if((job.total_positions <= length(GLOB.player_list) * (max_relative_positions / 100))) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) + return TRUE + return FALSE + +/datum/computer_file/program/job_management/proc/can_close_job(datum/job/job) + if(!(job?.title in blacklisted)) + if(job.total_positions > length(GLOB.player_list) * (max_relative_positions / 100)) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) + return TRUE + return FALSE + +/datum/computer_file/program/job_management/ui_act(action, params, datum/tgui/ui) + if(..()) + return + + var/authed = FALSE + var/mob/user = usr + var/obj/item/card/id/user_id = user.get_idcard() + if(user_id) + if(ACCESS_CHANGE_IDS in user_id.access) + authed = TRUE + + if(!authed) + return + + switch(action) + if("PRG_open_job") + var/edit_job_target = params["target"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j || !can_open_job(j)) + return + if(opened_positions[edit_job_target] >= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions++ + opened_positions[edit_job_target]++ + playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + return TRUE + if("PRG_close_job") + var/edit_job_target = params["target"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j || !can_close_job(j)) + return + //Allow instant closing without cooldown if a position has been opened before + if(opened_positions[edit_job_target] <= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions-- + opened_positions[edit_job_target]-- + playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + return TRUE + if("PRG_priority") + if(length(SSjob.prioritized_jobs) >= 5) + return + var/priority_target = params["target"] + var/datum/job/j = SSjob.GetJob(priority_target) + if(!j) + return + if(j.total_positions <= j.current_positions) + return + if(j in SSjob.prioritized_jobs) + SSjob.prioritized_jobs -= j + else + SSjob.prioritized_jobs += j + playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + return TRUE + + +/datum/computer_file/program/job_management/ui_data(mob/user) + var/list/data = get_header_data() + + var/authed = FALSE + var/obj/item/card/id/user_id = user.get_idcard(FALSE) + if(user_id) + if(ACCESS_CHANGE_IDS in user_id.access) + authed = TRUE + + data["authed"] = authed + + var/list/pos = list() + for(var/j in SSjob.occupations) + var/datum/job/job = j + if(job.title in blacklisted) + continue + + pos += list(list( + "title" = job.title, + "current" = job.current_positions, + "total" = job.total_positions, + "status_open" = authed ? can_open_job(job) : FALSE, + "status_close" = authed ? can_close_job(job) : FALSE, + )) + data["slots"] = pos + var/delta = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + data["cooldown"] = delta < 0 ? 0 : delta + var/list/priority = list() + for(var/j in SSjob.prioritized_jobs) + var/datum/job/job = j + priority += job.title + data["prioritized"] = priority + return data + diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm index 92e1453dc6..80c014bd03 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -10,7 +10,9 @@ requires_ntnet_feature = NTNET_SOFTWAREDOWNLOAD available_on_ntnet = 0 ui_header = "downloader_finished.gif" - tgui_id = "ntos_net_downloader" + tgui_id = "NtosNetDownloader" + ui_x = 480 + ui_y = 735 var/datum/computer_file/program/downloaded_file = null var/hacked_download = 0 @@ -168,4 +170,4 @@ /datum/computer_file/program/ntnetdownload/kill_program(forced) abort_file_download() - return ..(forced) \ No newline at end of file + return ..(forced) diff --git a/code/modules/modular_computers/file_system/programs/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/ntmonitor.dm index 2312db7b11..656c7614ab 100644 --- a/code/modules/modular_computers/file_system/programs/ntmonitor.dm +++ b/code/modules/modular_computers/file_system/programs/ntmonitor.dm @@ -6,8 +6,8 @@ size = 12 requires_ntnet = 1 required_access = ACCESS_NETWORK //NETWORK CONTROL IS A MORE SECURE PROGRAM. - available_on_ntnet = 1 - tgui_id = "ntos_net_monitor" + available_on_ntnet = TRUE + tgui_id = "NtosNetMonitor" /datum/computer_file/program/ntnetmonitor/ui_act(action, params) if(..()) @@ -78,4 +78,4 @@ data["ntnetlogs"] += list(list("entry" = i)) data["ntnetmaxlogs"] = SSnetworks.station_network.setting_maxlogcount - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm index d8b3f96f42..4ae5bd326b 100644 --- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm @@ -9,7 +9,7 @@ network_destination = "NTNRC server" ui_header = "ntnrc_idle.gif" available_on_ntnet = 1 - tgui_id = "ntos_net_chat" + tgui_id = "NtosNetChat" ui_x = 900 ui_y = 675 diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm index f7c734667b..d2d57b1447 100644 --- a/code/modules/modular_computers/file_system/programs/powermonitor.dm +++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm @@ -11,8 +11,7 @@ requires_ntnet = 0 network_destination = "power monitoring system" size = 9 - tgui_id = "ntos_power_monitor" - ui_style = "ntos" + tgui_id = "NtosPowerMonitor" ui_x = 550 ui_y = 700 diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm new file mode 100644 index 0000000000..910f923327 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -0,0 +1,86 @@ + +/datum/computer_file/program/robocontrol + filename = "robocontrol" + filedesc = "Bot Remote Controller" + program_icon_state = "robot" + extended_desc = "A remote controller used for giving basic commands to non-sentient robots." + transfer_access = ACCESS_ROBOTICS + requires_ntnet = TRUE + network_destination = "robotics control network" + size = 12 + tgui_id = "NtosRoboControl" + ui_x = 550 + ui_y = 550 + ///Number of simple robots on-station. + var/botcount = 0 + ///Used to find the location of the user for the purposes of summoning robots. + var/mob/current_user + ///Access granted by the used to summon robots. + var/list/current_access = list() + +/datum/computer_file/program/robocontrol/ui_data(mob/user) + var/list/data = get_header_data() + var/turf/current_turf = get_turf(ui_host()) + var/zlevel = current_turf.z + var/list/botlist = list() + var/list/mulelist = list() + + var/obj/item/computer_hardware/card_slot/card_slot = computer ? computer.all_components[MC_CARD] : null + data["have_id_slot"] = !!card_slot + if(computer) + var/obj/item/card/id/id_card = card_slot ? card_slot.stored_card : null + data["has_id"] = !!id_card + data["id_owner"] = id_card ? id_card.registered_name : "No Card Inserted." + data["access_on_card"] = id_card ? id_card.access : null + + botcount = 0 + current_user = user + + for(var/B in GLOB.bots_list) + var/mob/living/simple_animal/bot/Bot = B + if(!Bot.on || Bot.z != zlevel || Bot.remote_disabled) //Only non-emagged bots on the same Z-level are detected! + continue //Also, the PDA must have access to the bot type. + var/list/newbot = list("name" = Bot.name, "mode" = Bot.get_mode_ui(), "model" = Bot.model, "locat" = get_area(Bot), "bot_ref" = REF(Bot), "mule_check" = FALSE) + if(Bot.bot_type == MULE_BOT) + var/mob/living/simple_animal/bot/mulebot/MULE = Bot + mulelist += list(list("name" = MULE.name, "dest" = MULE.destination, "power" = MULE.cell ? MULE.cell.percent() : 0, "home" = MULE.home_destination, "autoReturn" = MULE.auto_return, "autoPickup" = MULE.auto_pickup, "reportDelivery" = MULE.report_delivery, "mule_ref" = REF(MULE))) + if(MULE.load) + data["load"] = MULE.load.name + newbot["mule_check"] = TRUE + botlist += list(newbot) + + data["bots"] = botlist + data["mules"] = mulelist + data["botcount"] = botlist.len + + return data + +/datum/computer_file/program/robocontrol/ui_act(action, list/params) + if(..()) + return TRUE + var/obj/item/computer_hardware/card_slot/card_slot + var/obj/item/card/id/id_card + if(computer) + card_slot = computer.all_components[MC_CARD] + if(card_slot) + id_card = card_slot.stored_card + + var/list/standard_actions = list("patroloff", "patrolon", "ejectpai") + var/list/MULE_actions = list("stop", "go", "home", "destination", "setid", "sethome", "unload", "autoret", "autopick", "report", "ejectpai") + var/mob/living/simple_animal/bot/Bot = locate(params["robot"]) in GLOB.bots_list + if (action in standard_actions) + Bot.bot_control(action, current_user, current_access) + if (action in MULE_actions) + Bot.bot_control(action, current_user, current_access, TRUE) + switch(action) + if("summon") + Bot.bot_control(action, current_user, id_card ? id_card.access : current_access) + if("ejectcard") + if(!computer || !card_slot) + return + if(id_card) + GLOB.data_core.manifest_modify(id_card.registered_name, id_card.assignment) + card_slot.try_eject(TRUE, current_user) + else + playsound(get_turf(ui_host()) , 'sound/machines/buzz-sigh.ogg', 25, FALSE) + return diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index dbee59bb3e..0c30319c2a 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -8,8 +8,7 @@ transfer_access = ACCESS_CONSTRUCTION network_destination = "supermatter monitoring system" size = 5 - tgui_id = "ntos_supermatter_monitor" - ui_style = "ntos" + tgui_id = "NtosSupermatterMonitor" ui_x = 600 ui_y = 350 var/last_status = SUPERMATTER_INACTIVE @@ -124,4 +123,4 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) if(S.uid == newuid) active = S - return TRUE \ No newline at end of file + return TRUE diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm index 2bed456c41..ffda79dabc 100644 --- a/code/modules/modular_computers/laptop_vendor.dm +++ b/code/modules/modular_computers/laptop_vendor.dm @@ -232,7 +232,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if (!ui) - ui = new(user, src, ui_key, "computer_fabricator", "Personal Computer Vendor", ui_x, ui_y, state = state) + ui = new(user, src, ui_key, "ComputerFabricator", "Personal Computer Vendor", ui_x, ui_y, state = state) ui.open() /obj/machinery/lapvend/attackby(obj/item/I, mob/user) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 1b23b1bb5d..fa023b0e3a 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -859,7 +859,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "apc", name, 450, 460, master_ui, state) + ui = new(user, src, ui_key, "Apc", name, 450, 460, master_ui, state) ui.open() /obj/machinery/power/apc/ui_data(mob/user) diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 582d72f6c0..d2ffc216ba 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -28,7 +28,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF var/sprite_number = 0 -/obj/machinery/gravity_generator/safe_throw_at() +/obj/machinery/gravity_generator/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) return FALSE /obj/machinery/gravity_generator/ex_act(severity, target) @@ -56,7 +56,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne qdel(src) /obj/machinery/gravity_generator/proc/set_broken() - stat |= BROKEN + obj_break() /obj/machinery/gravity_generator/proc/set_fix() stat &= ~BROKEN @@ -116,6 +116,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne sprite_number = 8 use_power = IDLE_POWER_USE interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OFFLINE + ui_x = 400 + ui_y = 165 var/on = TRUE var/breaker = TRUE var/list/parts = list() @@ -187,14 +189,14 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne /obj/machinery/gravity_generator/main/attackby(obj/item/I, mob/user, params) switch(broken_state) if(GRAV_NEEDS_SCREWDRIVER) - if(istype(I, /obj/item/screwdriver)) + if(I.tool_behaviour == TOOL_SCREWDRIVER) to_chat(user, "You secure the screws of the framework.") I.play_tool_sound(src) broken_state++ update_icon() return if(GRAV_NEEDS_WELDING) - if(istype(I, /obj/item/weldingtool)) + if(I.tool_behaviour == TOOL_WELDER) if(I.use_tool(src, user, 0, volume=50, amount=1)) to_chat(user, "You mend the damaged framework.") broken_state++ @@ -206,14 +208,14 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne if(PS.get_amount() >= 10) PS.use(10) to_chat(user, "You add the plating to the framework.") - playsound(src.loc, 'sound/machines/click.ogg', 75, 1) + playsound(src.loc, 'sound/machines/click.ogg', 75, TRUE) broken_state++ update_icon() else to_chat(user, "You need 10 sheets of plasteel!") return if(GRAV_NEEDS_WRENCH) - if(istype(I, /obj/item/wrench)) + if(I.tool_behaviour == TOOL_WRENCH) to_chat(user, "You secure the plating to the framework.") I.play_tool_sound(src) set_fix() @@ -224,7 +226,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "gravity_generator", name, 400, 200, master_ui, state) + ui = new(user, src, ui_key, "GravityGenerator", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/gravity_generator/main/ui_data(mob/user) @@ -241,16 +243,18 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne /obj/machinery/gravity_generator/main/ui_act(action, params) if(..()) return + switch(action) if("gentoggle") breaker = !breaker investigate_log("was toggled [breaker ? "ON" : "OFF"] by [key_name(usr)].", INVESTIGATE_GRAVITY) set_power() + . = TRUE // Power and Icon States /obj/machinery/gravity_generator/main/power_change() - ..() + . = ..() investigate_log("has [stat & NOPOWER ? "lost" : "regained"] power.", INVESTIGATE_GRAVITY) set_power() @@ -313,7 +317,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne charge_count -= 2 if(charge_count % 4 == 0 && prob(75)) // Let them know it is charging/discharging. - playsound(src.loc, 'sound/effects/empulse.ogg', 100, 1) + playsound(src.loc, 'sound/effects/empulse.ogg', 100, TRUE) updateDialog() if(prob(25)) // To help stop "Your clothes feel warm." spam. diff --git a/code/modules/power/monitor.dm b/code/modules/power/monitor.dm index f4ee102ccc..974fb1b9e2 100644 --- a/code/modules/power/monitor.dm +++ b/code/modules/power/monitor.dm @@ -10,6 +10,9 @@ idle_power_usage = 20 active_power_usage = 100 circuit = /obj/item/circuitboard/computer/powermonitor + tgui_id = "PowerMonitor" + ui_x = 550 + ui_y = 700 var/obj/structure/cable/attached_wire var/obj/machinery/power/apc/local_apc @@ -19,8 +22,6 @@ var/record_interval = 50 var/next_record = 0 var/is_secret_monitor = FALSE - tgui_id = "power_monitor" - ui_style = "ntos" /obj/machinery/computer/monitor/secret //Hides the power monitor (such as ones on ruins & CentCom) from PDA's to prevent metagaming. name = "outdated power monitoring console" @@ -87,7 +88,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, tgui_id, name, 550, 700, master_ui, state) + ui = new(user, src, ui_key, "PowerMonitor", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/monitor/ui_data() diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index 3c20f2f69c..e2ec3378c2 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -1,4 +1,3 @@ - //Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power). /obj/machinery/power/port_gen name = "portable generator" @@ -8,10 +7,11 @@ density = TRUE anchored = FALSE use_power = NO_POWER_USE + ui_x = 450 + ui_y = 340 - var/active = 0 + var/active = FALSE var/power_gen = 5000 - var/recent_fault = 0 var/power_output = 1 var/consumption = 0 var/base_icon = "portgen0" @@ -27,8 +27,16 @@ QDEL_NULL(soundloop) return ..() +/obj/machinery/power/port_gen/should_have_node() + return anchored + +/obj/machinery/power/port_gen/connect_to_network() + if(!anchored) + return FALSE + . = ..() + /obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check. - return 1 + return TRUE /obj/machinery/power/port_gen/proc/UseFuel() //Placeholder for fuel use. return @@ -39,26 +47,38 @@ /obj/machinery/power/port_gen/proc/handleInactive() return +/obj/machinery/power/port_gen/proc/TogglePower() + if(active) + active = FALSE + update_icon() + soundloop.stop() + else if(HasFuel()) + active = TRUE + START_PROCESSING(SSmachines, src) + update_icon() + soundloop.start() + /obj/machinery/power/port_gen/update_icon_state() icon_state = "[base_icon]_[active]" /obj/machinery/power/port_gen/process() - if(active && HasFuel() && !crit_fail && anchored && powernet) - add_avail(power_gen * power_output) + if(active) + if(!HasFuel() || !anchored) + TogglePower() + return + if(powernet) + add_avail(power_gen * power_output) UseFuel() - src.updateDialog() - soundloop.start() - else - active = 0 handleInactive() - update_icon() - soundloop.stop() /obj/machinery/power/port_gen/examine(mob/user) . = ..() . += "It is[!active?"n't":""] running." +///////////////// +// P.A.C.M.A.N // +///////////////// /obj/machinery/power/port_gen/pacman name = "\improper P.A.C.M.A.N.-type portable generator" circuit = /obj/item/circuitboard/machine/pacman @@ -78,8 +98,8 @@ /obj/machinery/power/port_gen/pacman/Initialize() . = ..() - var/obj/sheet = new sheet_path(null) - sheet_name = sheet.name + var/obj/S = sheet_path + sheet_name = initial(S.name) /obj/machinery/power/port_gen/pacman/Destroy() DropFuel() @@ -100,16 +120,16 @@ /obj/machinery/power/port_gen/pacman/examine(mob/user) . = ..() - . += "The generator has [sheets] units of [sheet_name] fuel left, producing [power_gen] per cycle." - if(crit_fail) - . += "The generator seems to have broken down." + . += "The generator has [sheets] units of [sheet_name] fuel left, producing [DisplayPower(power_gen)] per cycle." + if(anchored) + . += "It is anchored to the ground." if(in_range(user, src) || isobserver(user)) . += "The status display reads: Fuel efficiency increased by [(consumption*100)-100]%." /obj/machinery/power/port_gen/pacman/HasFuel() if(sheets >= 1 / (time_per_sheet / power_output) - sheet_left) - return 1 - return 0 + return TRUE + return FALSE /obj/machinery/power/port_gen/pacman/DropFuel() if(sheets) @@ -145,13 +165,11 @@ if (current_heat > 300) overheat() qdel(src) - return /obj/machinery/power/port_gen/pacman/handleInactive() - - if (current_heat > 0) - current_heat = max(current_heat - 2, 0) - src.updateDialog() + current_heat = max(current_heat - 2, 0) + if(current_heat == 0) + STOP_PROCESSING(SSmachines, src) /obj/machinery/power/port_gen/pacman/proc/overheat() explosion(src.loc, 2, 5, 2, -1) @@ -166,24 +184,21 @@ to_chat(user, "You add [amount] sheets to the [src.name].") sheets += amount addstack.use(amount) - updateUsrDialog() return else if(!active) - - if(istype(O, /obj/item/wrench)) - + if(O.tool_behaviour == TOOL_WRENCH) if(!anchored && !isinspace()) + anchored = TRUE connect_to_network() to_chat(user, "You secure the generator to the floor.") - anchored = TRUE else if(anchored) + anchored = FALSE disconnect_from_network() to_chat(user, "You unsecure the generator from the floor.") - anchored = FALSE - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) return - else if(istype(O, /obj/item/screwdriver)) + else if(O.tool_behaviour == TOOL_SCREWDRIVER) panel_open = !panel_open O.play_tool_sound(src) if(panel_open) @@ -196,12 +211,10 @@ return ..() /obj/machinery/power/port_gen/pacman/emag_act(mob/user) - . = ..() if(obj_flags & EMAGGED) return obj_flags |= EMAGGED emp_act(EMP_HEAVY) - return TRUE /obj/machinery/power/port_gen/pacman/attack_ai(mob/user) interact(user) @@ -209,60 +222,52 @@ /obj/machinery/power/port_gen/pacman/attack_paw(mob/user) interact(user) -/obj/machinery/power/port_gen/pacman/ui_interact(mob/user) - . = ..() - if (get_dist(src, user) > 1 ) - if(!isAI(user)) - user.unset_machine() - user << browse(null, "window=port_gen") - return +/obj/machinery/power/port_gen/pacman/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "PortableGenerator", name, ui_x, ui_y, master_ui, state) + ui.open() - var/dat = text("[name]
") - if (active) - dat += text("Generator: On
") - else - dat += text("Generator: Off
") - dat += text("[capitalize(sheet_name)]: [sheets] - Eject
") - var/stack_percent = round(sheet_left * 100, 1) - dat += text("Current stack: [stack_percent]%
") - dat += text("Power output: - [power_gen * power_output] +
") - dat += text("Power current: [(powernet == null ? "Unconnected" : "[DisplayPower(avail())]")]
") - dat += text("Heat: [current_heat]
") - dat += "
Close" - user << browse(dat, "window=port_gen") - onclose(user, "port_gen") +/obj/machinery/power/port_gen/pacman/ui_data() + var/data = list() -/obj/machinery/power/port_gen/pacman/Topic(href, href_list) + data["active"] = active + data["sheet_name"] = capitalize(sheet_name) + data["sheets"] = sheets + data["stack_percent"] = round(sheet_left * 100, 0.1) + + data["anchored"] = anchored + data["connected"] = (powernet == null ? 0 : 1) + data["ready_to_boot"] = anchored && HasFuel() + data["power_generated"] = DisplayPower(power_gen) + data["power_output"] = DisplayPower(power_gen * power_output) + data["power_available"] = (powernet == null ? 0 : DisplayPower(avail())) + data["current_heat"] = current_heat + . = data + +/obj/machinery/power/port_gen/pacman/ui_act(action, params) if(..()) return + switch(action) + if("toggle_power") + TogglePower() + . = TRUE - src.add_fingerprint(usr) - if(href_list["action"]) - if(href_list["action"] == "enable") - if(!active && HasFuel() && !crit_fail) - active = 1 - src.updateUsrDialog() - update_icon() - if(href_list["action"] == "disable") - if (active) - active = 0 - src.updateUsrDialog() - update_icon() - if(href_list["action"] == "eject") + if("eject") if(!active) DropFuel() - src.updateUsrDialog() - if(href_list["action"] == "lower_power") + . = TRUE + + if("lower_power") if (power_output > 1) power_output-- - src.updateUsrDialog() - if (href_list["action"] == "higher_power") + . = TRUE + + if("higher_power") if (power_output < 4 || (obj_flags & EMAGGED)) power_output++ - src.updateUsrDialog() - if (href_list["action"] == "close") - usr << browse(null, "window=port_gen") - usr.unset_machine() + . = TRUE /obj/machinery/power/port_gen/pacman/super name = "\improper S.U.P.E.R.P.A.C.M.A.N.-type portable generator" diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/singularity/particle_accelerator/particle_control.dm index af83f7ebca..12132b5bae 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_control.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_control.dm @@ -9,15 +9,17 @@ idle_power_usage = 500 active_power_usage = 10000 dir = NORTH - var/strength_upper_limit = 2 - var/interface_control = 1 - var/list/obj/structure/particle_accelerator/connected_parts - var/assembled = 0 - var/construction_state = PA_CONSTRUCTION_UNSECURED - var/active = 0 - var/strength = 0 - var/powered = 0 mouse_opacity = MOUSE_OPACITY_OPAQUE + ui_x = 350 + ui_y = 185 + var/strength_upper_limit = 2 + var/interface_control = TRUE + var/list/obj/structure/particle_accelerator/connected_parts + var/assembled = FALSE + var/construction_state = PA_CONSTRUCTION_UNSECURED + var/active = FALSE + var/strength = 0 + var/powered = FALSE /obj/machinery/particle_accelerator/control_box/Initialize() . = ..() @@ -34,30 +36,27 @@ QDEL_NULL(wires) return ..() -/obj/machinery/particle_accelerator/control_box/attack_hand(mob/user) +/obj/machinery/particle_accelerator/control_box/multitool_act(mob/living/user, obj/item/I) . = ..() - if(.) - return - if(construction_state == PA_CONSTRUCTION_COMPLETE) - interact(user) - else if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) wires.interact(user) + return TRUE /obj/machinery/particle_accelerator/control_box/proc/update_state() if(construction_state < PA_CONSTRUCTION_COMPLETE) use_power = NO_POWER_USE - assembled = 0 - active = 0 + assembled = FALSE + active = FALSE for(var/CP in connected_parts) var/obj/structure/particle_accelerator/part = CP part.strength = null - part.powered = 0 + part.powered = FALSE part.update_icon() connected_parts.Cut() return if(!part_scan()) use_power = IDLE_POWER_USE - active = 0 + active = FALSE connected_parts.Cut() /obj/machinery/particle_accelerator/control_box/update_icon_state() @@ -78,36 +77,6 @@ else icon_state = "control_boxc" -/obj/machinery/particle_accelerator/control_box/Topic(href, href_list) - if(..()) - return - - if(!interface_control) - to_chat(usr, "ERROR: Request timed out. Check wire contacts.") - return - - if(href_list["close"]) - usr << browse(null, "window=pacontrol") - usr.unset_machine() - return - if(href_list["togglep"]) - if(!wires.is_cut(WIRE_POWER)) - toggle_power() - - else if(href_list["scan"]) - part_scan() - - else if(href_list["strengthup"]) - if(!wires.is_cut(WIRE_STRENGTH)) - add_strength() - - else if(href_list["strengthdown"]) - if(!wires.is_cut(WIRE_STRENGTH)) - remove_strength() - - updateDialog() - update_icon() - /obj/machinery/particle_accelerator/control_box/proc/strength_change() for(var/CP in connected_parts) var/obj/structure/particle_accelerator/part = CP @@ -123,7 +92,6 @@ log_game("PA Control Computer increased to [strength] by [key_name(usr)] in [AREACOORD(src)]") investigate_log("increased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) - /obj/machinery/particle_accelerator/control_box/proc/remove_strength(s) if(assembled && (strength > 0)) strength-- @@ -133,11 +101,10 @@ log_game("PA Control Computer decreased to [strength] by [key_name(usr)] in [AREACOORD(src)]") investigate_log("decreased to [strength] by [key_name(usr)] at [AREACOORD(src)]", INVESTIGATE_SINGULO) - /obj/machinery/particle_accelerator/control_box/power_change() - ..() + . = ..() if(stat & NOPOWER) - active = 0 + active = FALSE use_power = NO_POWER_USE else if(!stat && construction_state == PA_CONSTRUCTION_COMPLETE) use_power = IDLE_POWER_USE @@ -160,49 +127,48 @@ var/odir = turn(dir,180) var/turf/T = loc - assembled = 0 + assembled = FALSE critical_machine = FALSE var/obj/structure/particle_accelerator/fuel_chamber/F = locate() in orange(1,src) if(!F) - return 0 + return FALSE setDir(F.dir) connected_parts.Cut() T = get_step(T,rdir) if(!check_part(T, /obj/structure/particle_accelerator/fuel_chamber)) - return 0 + return FALSE T = get_step(T,odir) if(!check_part(T, /obj/structure/particle_accelerator/end_cap)) - return 0 + return FALSE T = get_step(T,dir) T = get_step(T,dir) if(!check_part(T, /obj/structure/particle_accelerator/power_box)) - return 0 + return FALSE T = get_step(T,dir) if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/center)) - return 0 + return FALSE T = get_step(T,ldir) if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/left)) - return 0 + return FALSE T = get_step(T,rdir) T = get_step(T,rdir) if(!check_part(T, /obj/structure/particle_accelerator/particle_emitter/right)) - return 0 + return FALSE - assembled = 1 + assembled = TRUE critical_machine = TRUE //Only counts if the PA is actually assembled. - return 1 + return TRUE /obj/machinery/particle_accelerator/control_box/proc/check_part(turf/T, type) var/obj/structure/particle_accelerator/PA = locate(/obj/structure/particle_accelerator) in T if(istype(PA, type) && (PA.construction_state == PA_CONSTRUCTION_COMPLETE)) if(PA.connect_master(src)) connected_parts.Add(PA) - return 1 - return 0 - + return TRUE + return FALSE /obj/machinery/particle_accelerator/control_box/proc/toggle_power() active = !active @@ -214,47 +180,16 @@ for(var/CP in connected_parts) var/obj/structure/particle_accelerator/part = CP part.strength = strength - part.powered = 1 + part.powered = TRUE part.update_icon() else use_power = IDLE_POWER_USE for(var/CP in connected_parts) var/obj/structure/particle_accelerator/part = CP part.strength = null - part.powered = 0 + part.powered = FALSE part.update_icon() - return 1 - - -/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user) - . = ..() - if((get_dist(src, user) > 1) || (stat & (BROKEN|NOPOWER))) - if(!issilicon(user)) - user.unset_machine() - user << browse(null, "window=pacontrol") - return - - var/dat = "" - dat += "Close

" - dat += "

Status

" - if(!assembled) - dat += "Unable to detect all parts!
" - dat += "Run Scan

" - else - dat += "All parts in place.

" - dat += "Power:" - if(active) - dat += "On
" - else - dat += "Off
" - dat += "Toggle Power

" - dat += "Particle Strength: [strength] " - dat += "--|++

" - - var/datum/browser/popup = new(user, "pacontrol", name, 420, 300) - popup.set_content(dat) - popup.set_title_image(user.browse_rsc_icon(icon, icon_state)) - popup.open() + return TRUE /obj/machinery/particle_accelerator/control_box/examine(mob/user) . = ..() @@ -266,49 +201,48 @@ if(PA_CONSTRUCTION_PANEL_OPEN) . += "The panel is open." - /obj/machinery/particle_accelerator/control_box/attackby(obj/item/W, mob/user, params) var/did_something = FALSE switch(construction_state) if(PA_CONSTRUCTION_UNSECURED) - if(istype(W, /obj/item/wrench) && !isinspace()) + if(W.tool_behaviour == TOOL_WRENCH && !isinspace()) W.play_tool_sound(src, 75) anchored = TRUE - user.visible_message("[user.name] secures the [name] to the floor.", \ - "You secure the external bolts.") + user.visible_message("[user.name] secures the [name] to the floor.", \ + "You secure the external bolts.") construction_state = PA_CONSTRUCTION_UNWIRED did_something = TRUE if(PA_CONSTRUCTION_UNWIRED) - if(istype(W, /obj/item/wrench)) + if(W.tool_behaviour == TOOL_WRENCH) W.play_tool_sound(src, 75) anchored = FALSE - user.visible_message("[user.name] detaches the [name] from the floor.", \ - "You remove the external bolts.") + user.visible_message("[user.name] detaches the [name] from the floor.", \ + "You remove the external bolts.") construction_state = PA_CONSTRUCTION_UNSECURED did_something = TRUE else if(istype(W, /obj/item/stack/cable_coil)) var/obj/item/stack/cable_coil/CC = W if(CC.use(1)) - user.visible_message("[user.name] adds wires to the [name].", \ - "You add some wires.") + user.visible_message("[user.name] adds wires to the [name].", \ + "You add some wires.") construction_state = PA_CONSTRUCTION_PANEL_OPEN did_something = TRUE if(PA_CONSTRUCTION_PANEL_OPEN) - if(istype(W, /obj/item/wirecutters))//TODO:Shock user if its on? - user.visible_message("[user.name] removes some wires from the [name].", \ - "You remove some wires.") + if(W.tool_behaviour == TOOL_WIRECUTTER)//TODO:Shock user if its on? + user.visible_message("[user.name] removes some wires from the [name].", \ + "You remove some wires.") construction_state = PA_CONSTRUCTION_UNWIRED did_something = TRUE - else if(istype(W, /obj/item/screwdriver)) - user.visible_message("[user.name] closes the [name]'s access panel.", \ - "You close the access panel.") + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user.name] closes the [name]'s access panel.", \ + "You close the access panel.") construction_state = PA_CONSTRUCTION_COMPLETE did_something = TRUE if(PA_CONSTRUCTION_COMPLETE) - if(istype(W, /obj/item/screwdriver)) - user.visible_message("[user.name] opens the [name]'s access panel.", \ - "You open the access panel.") + if(W.tool_behaviour == TOOL_SCREWDRIVER) + user.visible_message("[user.name] opens the [name]'s access panel.", \ + "You open the access panel.") construction_state = PA_CONSTRUCTION_PANEL_OPEN did_something = TRUE @@ -324,6 +258,65 @@ if(prob(50)) qdel(src) +/obj/machinery/particle_accelerator/control_box/interact(mob/user) + if(construction_state == PA_CONSTRUCTION_PANEL_OPEN) + wires.interact(user) + else + ..() + +/obj/machinery/particle_accelerator/control_box/proc/is_interactive(mob/user) + if(!interface_control) + to_chat(user, "ERROR: Request timed out. Check wire contacts.") + return FALSE + if(construction_state != PA_CONSTRUCTION_COMPLETE) + return FALSE + return TRUE + +/obj/machinery/particle_accelerator/control_box/ui_status(mob/user) + if(is_interactive(user)) + return ..() + return UI_CLOSE + +/obj/machinery/particle_accelerator/control_box/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "ParticleAccelerator", name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/machinery/particle_accelerator/control_box/ui_data(mob/user) + var/list/data = list() + data["assembled"] = assembled + data["power"] = active + data["strength"] = strength + return data + +/obj/machinery/particle_accelerator/control_box/ui_act(action, params) + if(..()) + return + + switch(action) + if("power") + if(wires.is_cut(WIRE_POWER)) + return + toggle_power() + . = TRUE + if("scan") + part_scan() + . = TRUE + if("add_strength") + if(wires.is_cut(WIRE_STRENGTH)) + return + add_strength() + . = TRUE + if("remove_strength") + if(wires.is_cut(WIRE_STRENGTH)) + return + remove_strength() + . = TRUE + + update_icon() + #undef PA_CONSTRUCTION_UNSECURED #undef PA_CONSTRUCTION_UNWIRED #undef PA_CONSTRUCTION_PANEL_OPEN diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index 47de07cd71..5aa60a245b 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -313,7 +313,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "smes", name, 340, 440, master_ui, state) + ui = new(user, src, ui_key, "Smes", name, 340, 440, master_ui, state) ui.open() /obj/machinery/power/smes/ui_data() diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index 73ea3ccd59..d228a1e1f0 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -352,7 +352,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "solar_control", name, 380, 230, master_ui, state) + ui = new(user, src, ui_key, "SolarControl", name, 380, 230, master_ui, state) ui.open() /obj/machinery/power/solar_control/ui_data() diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm index b40e5c6c41..e6cacd81f1 100644 --- a/code/modules/power/turbine.dm +++ b/code/modules/power/turbine.dm @@ -41,6 +41,11 @@ var/comp_id = 0 var/efficiency +/obj/machinery/power/compressor/Destroy() + if(turbine && turbine.compressor == src) + turbine.compressor = null + turbine = null + return ..() /obj/machinery/power/turbine name = "gas turbine generator" @@ -51,12 +56,20 @@ resistance_flags = FIRE_PROOF CanAtmosPass = ATMOS_PASS_DENSITY circuit = /obj/item/circuitboard/machine/power_turbine + ui_x = 310 + ui_y = 150 var/opened = 0 var/obj/machinery/power/compressor/compressor var/turf/outturf var/lastgen var/productivity = 1 +/obj/machinery/power/turbine/Destroy() + if(compressor && compressor.turbine == src) + compressor.turbine = null + compressor = null + return ..() + // the inlet stage of the gas turbine electricity generator /obj/machinery/power/compressor/Initialize() @@ -66,12 +79,10 @@ inturf = get_step(src, dir) locate_machinery() if(!turbine) - stat |= BROKEN - + obj_break() #define COMPFRICTION 5e5 - /obj/machinery/power/compressor/locate_machinery() if(turbine) return @@ -103,7 +114,7 @@ stat &= ~BROKEN else to_chat(user, "Turbine not connected.") - stat |= BROKEN + obj_break() return default_deconstruction_crowbar(I) @@ -129,9 +140,9 @@ // RPM function to include compression friction - be advised that too low/high of a compfriction value can make things screwy + rpm = min(rpm, (COMPFRICTION*efficiency)/2) rpm = max(0, rpm - (rpm*rpm)/(COMPFRICTION*efficiency)) - if(starter && !(stat & NOPOWER)) use_power(2800) if(rpm<1000) @@ -140,8 +151,6 @@ if(rpm<1000) rpmtarget = 0 - - if(rpm>50000) add_overlay(mutable_appearance(icon, "comp-o4", FLY_LAYER)) else if(rpm>10000) @@ -164,7 +173,7 @@ outturf = get_step(src, dir) locate_machinery() if(!compressor) - stat |= BROKEN + obj_break() connect_to_network() /obj/machinery/power/turbine/RefreshParts() @@ -222,8 +231,6 @@ if(lastgen > 100) add_overlay(mutable_appearance(icon, "turb-o", FLY_LAYER)) - updateDialog() - /obj/machinery/power/turbine/attackby(obj/item/I, mob/user, params) if(default_deconstruction_screwdriver(user, initial(icon_state), initial(icon_state), I)) return @@ -237,53 +244,42 @@ stat &= ~BROKEN else to_chat(user, "Compressor not connected.") - stat |= BROKEN + obj_break() return default_deconstruction_crowbar(I) -/obj/machinery/power/turbine/ui_interact(mob/user) +/obj/machinery/power/turbine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "TurbineComputer", name, ui_x, ui_y, master_ui, state) + ui.open() - if(!Adjacent(user) || (stat & (NOPOWER|BROKEN)) && !issilicon(user)) - user.unset_machine(src) - user << browse(null, "window=turbine") - return +/obj/machinery/power/turbine/ui_data(mob/user) + var/list/data = list() + data["compressor"] = compressor ? TRUE : FALSE + data["compressor_broke"] = (!compressor || (compressor.stat & BROKEN)) ? TRUE : FALSE + data["turbine"] = compressor?.turbine ? TRUE : FALSE + data["turbine_broke"] = (!compressor || !compressor.turbine || (compressor.turbine.stat & BROKEN)) ? TRUE : FALSE + data["online"] = compressor?.starter + data["power"] = DisplayPower(compressor?.turbine?.lastgen) + data["rpm"] = compressor?.rpm + data["temp"] = compressor?.gas_contained.temperature + return data - var/t = "Gas Turbine Generator
"
-
-	t += "Generated power : [DisplayPower(lastgen)]

" - - t += "Turbine: [round(compressor.rpm)] RPM
" - - t += "Starter: [ compressor.starter ? "Off On" : "Off On"]" - - t += "

Close" - - t += "
" - var/datum/browser/popup = new(user, "turbine", name) - popup.set_content(t) - popup.open() - - return - -/obj/machinery/power/turbine/Topic(href, href_list) +/obj/machinery/power/turbine/ui_act(action, params) if(..()) return - if( href_list["close"] ) - usr << browse(null, "window=turbine") - usr.unset_machine(src) - return - - else if( href_list["str"] ) - if(compressor) - compressor.starter = !compressor.starter - - updateDialog() - - - - + switch(action) + if("toggle_power") + if(compressor && compressor.turbine) + compressor.starter = !compressor.starter + . = TRUE + if("reconnect") + locate_machinery() + . = TRUE ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -296,6 +292,8 @@ icon_screen = "turbinecomp" icon_keyboard = "tech_key" circuit = /obj/item/circuitboard/computer/turbine_computer + ui_x = 310 + ui_y = 150 var/obj/machinery/power/compressor/compressor var/id = 0 @@ -319,27 +317,25 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "turbine_computer", name, 300, 200, master_ui, state) + ui = new(user, src, ui_key, "TurbineComputer", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/turbine_computer/ui_data(mob/user) var/list/data = list() - data["compressor"] = compressor ? TRUE : FALSE data["compressor_broke"] = (!compressor || (compressor.stat & BROKEN)) ? TRUE : FALSE data["turbine"] = compressor?.turbine ? TRUE : FALSE data["turbine_broke"] = (!compressor || !compressor.turbine || (compressor.turbine.stat & BROKEN)) ? TRUE : FALSE data["online"] = compressor?.starter - data["power"] = DisplayPower(compressor?.turbine?.lastgen) data["rpm"] = compressor?.rpm data["temp"] = compressor?.gas_contained.temperature - return data /obj/machinery/computer/turbine_computer/ui_act(action, params) if(..()) return + switch(action) if("toggle_power") if(compressor && compressor.turbine) diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index 7f57dd1723..d6d9dd5d7e 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -182,7 +182,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "chem_dispenser", name, 565, 550, master_ui, state) + ui = new(user, src, ui_key, "ChemDispenser", name, 565, 550, master_ui, state) if(user.hallucinating()) ui.set_autoupdate(FALSE) //to not ruin the immersion by constantly changing the fake chemicals ui.open() diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 63e9d724a4..d2e7b5f883 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -103,7 +103,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "chem_heater", name, 275, 400, master_ui, state) + ui = new(user, src, ui_key, "ChemHeater", name, 275, 400, master_ui, state) ui.open() /obj/machinery/chem_heater/ui_data() @@ -155,4 +155,4 @@ if("eject") on = FALSE replace_beaker(usr) - . = TRUE \ No newline at end of file + . = TRUE diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 9899d219bf..d5cd4ab8d0 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -160,7 +160,8 @@ if(!ui) var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/simple/pills) assets.send(user) - ui = new(user, src, ui_key, "chem_master", name, 500, 550, master_ui, state) + + ui = new(user, src, ui_key, "ChemMaster", name, 500, 550, master_ui, state) ui.open() //Insert our custom spritesheet css link into the html diff --git a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm index ed23e7c75c..66c2616972 100644 --- a/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm +++ b/code/modules/reagents/chemistry/machinery/chem_synthesizer.dm @@ -16,7 +16,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "chem_synthesizer", name, 390, 330, master_ui, state) + ui = new(user, src, ui_key, "ChemDebugSynthesizer", name, 390, 330, master_ui, state) ui.open() /obj/machinery/chem_dispenser/chem_synthesizer/ui_act(action, params) diff --git a/code/modules/reagents/chemistry/machinery/pandemic.dm b/code/modules/reagents/chemistry/machinery/pandemic.dm index 25315f457d..a6f5468c74 100644 --- a/code/modules/reagents/chemistry/machinery/pandemic.dm +++ b/code/modules/reagents/chemistry/machinery/pandemic.dm @@ -123,7 +123,7 @@ /obj/machinery/computer/pandemic/ui_interact(mob/user, ui_key = "main", datum/tgui/ui, force_open = FALSE, datum/tgui/master_ui, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "pandemic", name, 520, 550, master_ui, state) + ui = new(user, src, ui_key, "Pandemic", name, 520, 550, master_ui, state) ui.open() /obj/machinery/computer/pandemic/ui_data(mob/user) diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm index 0a08395c1b..cac90d1c14 100644 --- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm +++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm @@ -104,7 +104,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "smoke_machine", name, 350, 350, master_ui, state) + ui = new(user, src, ui_key, "SmokeMachine", name, 350, 350, master_ui, state) ui.open() /obj/machinery/smoke_machine/ui_data(mob/user) diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index 65bfa1d98f..c4d9afd448 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -305,7 +305,7 @@ return ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "disposal_unit", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "DisposalUnit", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/disposal/bin/ui_data(mob/user) diff --git a/code/modules/research/nanites/nanite_chamber_computer.dm b/code/modules/research/nanites/nanite_chamber_computer.dm index 0750d3d268..b9623b751d 100644 --- a/code/modules/research/nanites/nanite_chamber_computer.dm +++ b/code/modules/research/nanites/nanite_chamber_computer.dm @@ -3,8 +3,8 @@ desc = "Controls a connected nanite chamber. Can inoculate nanites, load programs, and analyze existing nanite swarms." var/obj/machinery/nanite_chamber/chamber var/obj/item/disk/nanite_program/disk - circuit = /obj/item/circuitboard/computer/nanite_chamber_control icon_screen = "nanite_chamber_control" + circuit = /obj/item/circuitboard/computer/nanite_chamber_control ui_x = 380 ui_y = 570 @@ -28,7 +28,7 @@ /obj/machinery/computer/nanite_chamber_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "nanite_chamber_control", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "NaniteChamberControl", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/nanite_chamber_control/ui_data() @@ -72,14 +72,14 @@ if("set_safety") var/threshold = text2num(params["value"]) if(!isnull(threshold)) - chamber.set_safety(CLAMP(round(threshold, 1),0,500)) + chamber.set_safety(clamp(round(threshold, 1),0,500)) playsound(src, "terminal_type", 25, FALSE) chamber.occupant.investigate_log("'s nanites' safety threshold was set to [threshold] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) . = TRUE if("set_cloud") var/cloud_id = text2num(params["value"]) if(!isnull(cloud_id)) - chamber.set_cloud(CLAMP(round(cloud_id, 1),0,100)) + chamber.set_cloud(clamp(round(cloud_id, 1),0,100)) playsound(src, "terminal_type", 25, FALSE) chamber.occupant.investigate_log("'s nanites' cloud id was set to [cloud_id] by [key_name(usr)] via [src] at [AREACOORD(src)].", INVESTIGATE_NANITES) . = TRUE diff --git a/code/modules/research/nanites/nanite_cloud_controller.dm b/code/modules/research/nanites/nanite_cloud_controller.dm index 439d0c5750..c688260329 100644 --- a/code/modules/research/nanites/nanite_cloud_controller.dm +++ b/code/modules/research/nanites/nanite_cloud_controller.dm @@ -56,7 +56,7 @@ /obj/machinery/computer/nanite_cloud_controller/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "nanite_cloud_control", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "NaniteCloudControl", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/nanite_cloud_controller/ui_data() diff --git a/code/modules/research/nanites/nanite_program_hub.dm b/code/modules/research/nanites/nanite_program_hub.dm index ea4392f236..abec47813d 100644 --- a/code/modules/research/nanites/nanite_program_hub.dm +++ b/code/modules/research/nanites/nanite_program_hub.dm @@ -50,7 +50,7 @@ /obj/machinery/nanite_program_hub/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "nanite_program_hub", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "NaniteProgramHub", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/nanite_program_hub/ui_data() diff --git a/code/modules/research/nanites/nanite_programmer.dm b/code/modules/research/nanites/nanite_programmer.dm index d81880c6d9..89614f428d 100644 --- a/code/modules/research/nanites/nanite_programmer.dm +++ b/code/modules/research/nanites/nanite_programmer.dm @@ -37,7 +37,7 @@ /obj/machinery/nanite_programmer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "nanite_programmer", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, "NaniteProgrammer", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/nanite_programmer/ui_data() diff --git a/code/modules/research/nanites/nanite_remote.dm b/code/modules/research/nanites/nanite_remote.dm index 71aecc8f2c..3ca49f6d67 100644 --- a/code/modules/research/nanites/nanite_remote.dm +++ b/code/modules/research/nanites/nanite_remote.dm @@ -83,7 +83,7 @@ /obj/item/nanite_remote/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.hands_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "nanite_remote", name, 420, 500, master_ui, state) + ui = new(user, src, ui_key, "NaniteRemote", name, 420, 500, master_ui, state) ui.open() /obj/item/nanite_remote/ui_data() diff --git a/code/modules/ruins/spaceruin_code/TheDerelict.dm b/code/modules/ruins/spaceruin_code/TheDerelict.dm index 58e257d587..2e2765b1a9 100644 --- a/code/modules/ruins/spaceruin_code/TheDerelict.dm +++ b/code/modules/ruins/spaceruin_code/TheDerelict.dm @@ -17,3 +17,196 @@ desc = "Looks like someone started shakily writing a will in space common, but were interrupted by something bloody..." info = "I, Victor Belyakov, do hereby leave my _- " + +/// Vault controller for use on the derelict/KS13. +/obj/machinery/computer/vaultcontroller + name = "vault controller" + desc = "It seems to be powering and controlling the vault locks." + icon_screen = "power" + icon_keyboard = "power_key" + light_color = LIGHT_COLOR_YELLOW + use_power = NO_POWER_USE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + var/obj/structure/cable/attached_cable + var/obj/machinery/door/airlock/vault/derelict/door1 + var/obj/machinery/door/airlock/vault/derelict/door2 + var/locked = TRUE + var/siphoned_power = 0 + var/siphon_max = 1e7 + + ui_x = 300 + ui_y = 120 + + +/obj/machinery/computer/monitor/examine(mob/user) + . = ..() + . += "It appears to be powered via a cable connector." + + +//Checks for cable connection, charges if possible. +/obj/machinery/computer/vaultcontroller/process() + if(siphoned_power >= siphon_max) + return + update_cable() + if(attached_cable) + attempt_siphon() + + +///Looks for a cable connection beneath the machine. +/obj/machinery/computer/vaultcontroller/proc/update_cable() + var/turf/T = get_turf(src) + attached_cable = locate(/obj/structure/cable) in T + + +///Initializes airlock links. +/obj/machinery/computer/vaultcontroller/proc/find_airlocks() + for(var/obj/machinery/door/airlock/A in GLOB.airlocks) + if(A.id_tag == "derelictvault") + if(!door1) + door1 = A + continue + if(door1 && !door2) + door2 = A + break + + +///Tries to charge from powernet excess, no upper limit except max charge. +/obj/machinery/computer/vaultcontroller/proc/attempt_siphon() + var/surpluspower = clamp(attached_cable.surplus(), 0, (siphon_max - siphoned_power)) + if(surpluspower) + attached_cable.add_load(surpluspower) + siphoned_power += surpluspower + + +///Handles the doors closing +/obj/machinery/computer/vaultcontroller/proc/cycle_close(obj/machinery/door/airlock/A) + A.safe = FALSE //Make sure its forced closed, always + A.unbolt() + A.close() + A.bolt() + + +///Handles the doors opening +/obj/machinery/computer/vaultcontroller/proc/cycle_open(obj/machinery/door/airlock/A) + A.unbolt() + A.open() + A.bolt() + + +///Attempts to lock the vault doors +/obj/machinery/computer/vaultcontroller/proc/lock_vault() + if(door1 && !door1.density) + cycle_close(door1) + if(door2 && !door2.density) + cycle_close(door2) + if(door1.density && door1.locked && door2.density && door2.locked) + locked = TRUE + + +///Attempts to unlock the vault doors +/obj/machinery/computer/vaultcontroller/proc/unlock_vault() + if(door1 && door1.density) + cycle_open(door1) + if(door2 && door2.density) + cycle_open(door2) + if(!door1.density && door1.locked && !door2.density && door2.locked) + locked = FALSE + + +///Attempts to lock/unlock vault doors, if machine is charged. +/obj/machinery/computer/vaultcontroller/proc/activate_lock() + if(siphoned_power < siphon_max) + return + if(!door1 || !door2) + find_airlocks() + if(locked) + unlock_vault() + else + lock_vault() + + +/obj/machinery/computer/vaultcontroller/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "VaultController", name, ui_x, ui_y, master_ui, state) + ui.open() + + +/obj/machinery/computer/vaultcontroller/ui_act(action, params) + if(..()) + return + switch(action) + if("togglelock") + activate_lock() + + +/obj/machinery/computer/vaultcontroller/ui_data() + var/list/data = list() + data["stored"] = siphoned_power + data["max"] = siphon_max + data["doorstatus"] = locked + return data + + +///Airlock that can't be deconstructed, broken or hacked. +/obj/machinery/door/airlock/vault/derelict + locked = TRUE + move_resist = INFINITY + use_power = NO_POWER_USE + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + id_tag = "derelictvault" + + +///Overrides screwdriver attack to prevent all deconstruction and hacking. +/obj/machinery/door/airlock/vault/derelict/attackby(obj/item/C, mob/user, params) + if(C.tool_behaviour == TOOL_SCREWDRIVER) + return + ..() + + +// So drones can teach borgs and AI dronespeak. For best effect, combine with mother drone lawset. +/obj/item/dronespeak_manual + name = "dronespeak manual" + desc = "The book's cover reads: \"Understanding Dronespeak - An exercise in futility.\"" + icon = 'icons/obj/library.dmi' + icon_state = "book2" + +/obj/item/dronespeak_manual/attack_self(mob/living/user) + ..() + if(isdrone(user) || issilicon(user)) + if(user.has_language(/datum/language/drone)) + to_chat(user, "You start skimming through [src], but you already know dronespeak.") + else + to_chat(user, "You start skimming through [src], and suddenly the drone chittering makes sense.") + user.grant_language(/datum/language/drone, TRUE, TRUE, LANGUAGE_MIND) + return + + if(user.has_language(/datum/language/drone)) + to_chat(user, "You start skimming through [src], but you already know dronespeak.") + else + to_chat(user, "You start skimming through [src], but you can't make any sense of the contents.") + +/obj/item/dronespeak_manual/attack(mob/living/M, mob/living/user) + if(!istype(M) || !istype(user)) + return + if(M == user) + attack_self(user) + return + + playsound(loc, "punch", 25, TRUE, -1) + if(isdrone(M) || issilicon(M)) + if(M.has_language(/datum/language/drone)) + M.visible_message("[user] beats [M] over the head with [src]!", "[user] beats you over the head with [src]!", "You hear smacking.") + else + M.visible_message("[user] teaches [M] by beating [M.p_them()] over the head with [src]!", "As [user] hits you with [src], chitters resonate in your mind.", "You hear smacking.") + M.grant_language(/datum/language/drone, TRUE, TRUE, LANGUAGE_MIND) + return + +/obj/structure/fluff/oldturret + name = "broken turret" + desc = "An obsolete model of turret, long non-functional." + icon = 'icons/obj/turrets.dmi' + icon_state = "turretCover" + density = TRUE diff --git a/code/modules/security_levels/keycard_authentication.dm b/code/modules/security_levels/keycard_authentication.dm index 8a58563407..fed256e98c 100644 --- a/code/modules/security_levels/keycard_authentication.dm +++ b/code/modules/security_levels/keycard_authentication.dm @@ -15,6 +15,9 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) power_channel = ENVIRON req_access = list(ACCESS_KEYCARD_AUTH) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + ui_x = 375 + ui_y = 125 + var/datum/callback/ev var/event = "" var/obj/machinery/keycard_auth/event_source @@ -34,7 +37,7 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "keycard_auth", name, 375, 125, master_ui, state) + ui = new(user, src, ui_key, "KeycardAuth", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/keycard_auth/ui_data() @@ -99,13 +102,13 @@ GLOBAL_DATUM_INIT(keycard_events, /datum/events, new) /obj/machinery/keycard_auth/proc/trigger_event(confirmer) log_game("[key_name(triggerer)] triggered and [key_name(confirmer)] confirmed event [event]") - message_admins("[key_name(triggerer)] triggered and [key_name(confirmer)] confirmed event [event]") + message_admins("[ADMIN_LOOKUPFLW(triggerer)] triggered and [ADMIN_LOOKUPFLW(confirmer)] confirmed event [event]") var/area/A1 = get_area(triggerer) - deadchat_broadcast("[triggerer] triggered [event] at [A1.name].", triggerer) + deadchat_broadcast(" triggered [event] at [A1.name].", "[triggerer]", triggerer, message_type=DEADCHAT_ANNOUNCEMENT) var/area/A2 = get_area(confirmer) - deadchat_broadcast("[confirmer] confirmed [event] at [A2.name].", confirmer) + deadchat_broadcast(" confirmed [event] at [A2.name].", "[confirmer]", confirmer, message_type=DEADCHAT_ANNOUNCEMENT) switch(event) if(KEYCARD_RED_ALERT) set_security_level(SEC_LEVEL_RED) diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index cd08b7290a..12466c0f03 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -49,8 +49,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "emergency_shuttle_console", name, - 400, 350, master_ui, state) + ui = new(user, src, ui_key, "EmergencyShuttleConsole", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/emergency_shuttle/ui_data() diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm index 183462a8f7..2ca0e65477 100644 --- a/code/modules/station_goals/bsa.dm +++ b/code/modules/station_goals/bsa.dm @@ -223,7 +223,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "bsa", name, 400, 220, master_ui, state) + ui = new(user, src, ui_key, "BluespaceArtillery", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/bsa_control/ui_data() diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm index 2aae17b0f3..3b0ab29308 100644 --- a/code/modules/station_goals/dna_vault.dm +++ b/code/modules/station_goals/dna_vault.dm @@ -178,7 +178,7 @@ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) roll_powers(user) - ui = new(user, src, ui_key, "dna_vault", name, 350, 400, master_ui, state) + ui = new(user, src, ui_key, "DnaVault", name, ui_x, ui_y, master_ui, state) ui.open() diff --git a/code/modules/station_goals/shield.dm b/code/modules/station_goals/shield.dm index cf0d79c742..299fda4a26 100644 --- a/code/modules/station_goals/shield.dm +++ b/code/modules/station_goals/shield.dm @@ -45,7 +45,7 @@ /obj/machinery/computer/sat_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "sat_control", name, 400, 305, master_ui, state) + ui = new(user, src, ui_key, "SatelliteControl", name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/sat_control/ui_act(action, params) diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 38a5a27e0c..5de54c439c 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -1,133 +1,142 @@ - /** - * tgui external - * - * Contains all external tgui declarations. - **/ +/** + * tgui external + * + * Contains all external tgui declarations. + */ - /** - * public - * - * Used to open and update UIs. - * If this proc is not implemented properly, the UI will not update correctly. - * - * required user mob The mob who opened/is using the UI. - * optional ui_key string The ui_key of the UI. - * optional ui datum/tgui The UI to be updated, if it exists. - * optional force_open bool If the UI should be re-opened instead of updated. - * optional master_ui datum/tgui The parent UI. - * optional state datum/ui_state The state used to determine status. - **/ +/** + * public + * + * Used to open and update UIs. + * If this proc is not implemented properly, the UI will not update correctly. + * + * required user mob The mob who opened/is using the UI. + * optional ui_key string The ui_key of the UI. + * optional ui datum/tgui The UI to be updated, if it exists. + * optional force_open bool If the UI should be re-opened instead of updated. + * optional master_ui datum/tgui The parent UI. + * optional state datum/ui_state The state used to determine status. + */ /datum/proc/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) return FALSE // Not implemented. - /** - * public - * - * Data to be sent to the UI. - * This must be implemented for a UI to work. - * - * required user mob The mob interacting with the UI. - * - * return list Data to be sent to the UI. - **/ +/** + * public + * + * Data to be sent to the UI. + * This must be implemented for a UI to work. + * + * required user mob The mob interacting with the UI. + * + * return list Data to be sent to the UI. + */ /datum/proc/ui_data(mob/user) return list() // Not implemented. - /** - * public - * - * Static Data to be sent to the UI. - * Static data differs from normal data in that it's large data that should be sent infrequently - * This is implemented optionally for heavy uis that would be sending a lot of redundant data - * frequently. - * Gets squished into one object on the frontend side, but the static part is cached. - * - * required user mob The mob interacting with the UI. - * - * return list Statuic Data to be sent to the UI. - **/ +/** + * public + * + * Static Data to be sent to the UI. + * Static data differs from normal data in that it's large data that should be sent infrequently + * This is implemented optionally for heavy uis that would be sending a lot of redundant data + * frequently. + * Gets squished into one object on the frontend side, but the static part is cached. + * + * required user mob The mob interacting with the UI. + * + * return list Statuic Data to be sent to the UI. + */ /datum/proc/ui_static_data(mob/user) return list() /** - * public - * - * Forces an update on static data. Should be done manually whenever something happens to change static data. - * - * required user the mob currently interacting with the ui - * optional ui ui to be updated - * optional ui_key ui key of ui to be updated - * -**/ + * public + * + * Forces an update on static data. Should be done manually whenever something happens to change static data. + * + * required user the mob currently interacting with the ui + * optional ui ui to be updated + * optional ui_key ui key of ui to be updated + */ /datum/proc/update_static_data(mob/user, datum/tgui/ui, ui_key = "main") ui = SStgui.try_update_ui(user, src, ui_key, ui) + // If there was no ui to update, there's no static data to update either. if(!ui) - return //If there was no ui to update, there's no static data to update either. + return ui.push_data(null, ui_static_data(), TRUE) - /** - * public - * - * Called on a UI when the UI receieves a href. - * Think of this as Topic(). - * - * required action string The action/button that has been invoked by the user. - * required params list A list of parameters attached to the button. - * - * return bool If the UI should be updated or not. - **/ +/** + * public + * + * Called on a UI when the UI receieves a href. + * Think of this as Topic(). + * + * required action string The action/button that has been invoked by the user. + * required params list A list of parameters attached to the button. + * + * return bool If the UI should be updated or not. + */ /datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + // If UI is not interactive or usr calling Topic is not the UI user, bail. if(!ui || ui.status != UI_INTERACTIVE) - return 1 // If UI is not interactive or usr calling Topic is not the UI user, bail. + return 1 - /** - * public - * - * Called on an object when a tgui object is being created, allowing you to customise the html - * For example: inserting a custom stylesheet that you need in the head - * - * For this purpose, some tags are available in the html, to be parsed out with replacetext - * (customheadhtml) - Additions to the head tag - * - * required html the html base text - * - **/ +/** + * public + * + * Called on an object when a tgui object is being created, allowing you to + * customise the html + * For example: inserting a custom stylesheet that you need in the head + * + * For this purpose, some tags are available in the html, to be parsed out + ^ with replacetext + * (customheadhtml) - Additions to the head tag + * + * required html the html base text + */ /datum/proc/ui_base_html(html) return html - /** - * private - * - * The UI's host object (usually src_object). - * This allows modules/datums to have the UI attached to them, - * and be a part of another object. - **/ +/** + * private + * + * The UI's host object (usually src_object). + * This allows modules/datums to have the UI attached to them, + * and be a part of another object. + */ /datum/proc/ui_host(mob/user) return src // Default src. - /** - * global - * - * Used to track UIs for a mob. - **/ -/mob/var/list/open_uis = list() - /** - * public - * - * Called on a UI's object when the UI is closed, not to be confused with client/verb/uiclose(), which closes the ui window - * - * - **/ -/datum/proc/ui_close() +/** + * global + * + * Associative list of JSON-encoded shared states that were set by + * tgui clients. + */ +/datum/var/list/tgui_shared_states - /** - * verb - * - * Called by UIs when they are closed. - * Must be a verb so winset() can call it. - * - * required uiref ref The UI that was closed. - **/ +/** + * global + * + * Used to track UIs for a mob. + */ +/mob/var/list/open_uis = list() +/** + * public + * + * Called on a UI's object when the UI is closed, not to be confused with + * client/verb/uiclose(), which closes the ui window + */ +/datum/proc/ui_close(mob/user) + +/** + * verb + * + * Called by UIs when they are closed. + * Must be a verb so winset() can call it. + * + * required uiref ref The UI that was closed. + */ /client/verb/uiclose(ref as text) // Name the verb, and hide it from the user panel. set name = "uiclose" diff --git a/code/modules/tgui/states.dm b/code/modules/tgui/states.dm index b0808cef5e..5ddbd81b25 100644 --- a/code/modules/tgui/states.dm +++ b/code/modules/tgui/states.dm @@ -1,19 +1,19 @@ - /** - * tgui states - * - * Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks. - **/ +/** + * tgui states + * + * Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks. + */ - /** - * public - * - * Checks the UI state for a mob. - * - * required user mob The mob who opened/is using the UI. - * required state datum/ui_state The state to check. - * - * return UI_state The state of the UI. - **/ +/** + * public + * + * Checks the UI state for a mob. + * + * required user mob The mob who opened/is using the UI. + * required state datum/ui_state The state to check. + * + * return UI_state The state of the UI. + */ /datum/proc/ui_status(mob/user, datum/ui_state/state) var/src_object = ui_host(user) . = UI_CLOSE @@ -34,27 +34,27 @@ var/result = state.can_use_topic(src_object, user) . = max(., result) - /** - * private - * - * Checks if a user can use src_object's UI, and returns the state. - * Can call a mob proc, which allows overrides for each mob. - * - * required src_object datum The object/datum which owns the UI. - * required user mob The mob who opened/is using the UI. - * - * return UI_state The state of the UI. - **/ +/** + * private + * + * Checks if a user can use src_object's UI, and returns the state. + * Can call a mob proc, which allows overrides for each mob. + * + * required src_object datum The object/datum which owns the UI. + * required user mob The mob who opened/is using the UI. + * + * return UI_state The state of the UI. + */ /datum/ui_state/proc/can_use_topic(src_object, mob/user) return UI_CLOSE // Don't allow interaction by default. - /** - * public - * - * Standard interaction/sanity checks. Different mob types may have overrides. - * - * return UI_state The state of the UI. - **/ +/** + * public + * + * Standard interaction/sanity checks. Different mob types may have overrides. + * + * return UI_state The state of the UI. + */ /mob/proc/shared_ui_interaction(src_object) if(!client) // Close UIs if mindless. return UI_CLOSE @@ -75,31 +75,31 @@ return ..() /** - * public - * - * Check the distance for a living mob. - * Really only used for checks outside the context of a mob. - * Otherwise, use shared_living_ui_distance(). - * - * required src_object The object which owns the UI. - * required user mob The mob who opened/is using the UI. - * - * return UI_state The state of the UI. - **/ + * public + * + * Check the distance for a living mob. + * Really only used for checks outside the context of a mob. + * Otherwise, use shared_living_ui_distance(). + * + * required src_object The object which owns the UI. + * required user mob The mob who opened/is using the UI. + * + * return UI_state The state of the UI. + */ /atom/proc/contents_ui_distance(src_object, mob/living/user) return user.shared_living_ui_distance(src_object) // Just call this mob's check. - /** - * public - * - * Distance versus interaction check. - * - * required src_object atom/movable The object which owns the UI. - * - * return UI_state The state of the UI. - **/ -/mob/living/proc/shared_living_ui_distance(atom/movable/src_object) - if(!(src_object in view(src))) // If the object is obscured, close it. +/** + * public + * + * Distance versus interaction check. + * + * required src_object atom/movable The object which owns the UI. + * + * return UI_state The state of the UI. + */ +/mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE) + if(viewcheck && !(src_object in view(src))) // If the object is obscured, close it. return UI_CLOSE var/dist = get_dist(src_object, src) diff --git a/code/modules/tgui/states/admin.dm b/code/modules/tgui/states/admin.dm index 945a864430..61fc373118 100644 --- a/code/modules/tgui/states/admin.dm +++ b/code/modules/tgui/states/admin.dm @@ -1,8 +1,8 @@ - /** - * tgui state: admin_state - * - * Checks that the user is an admin, end-of-story. - **/ +/** + * tgui state: admin_state + * + * Checks that the user is an admin, end-of-story. + */ GLOBAL_DATUM_INIT(admin_state, /datum/ui_state/admin_state, new) diff --git a/code/modules/tgui/states/always.dm b/code/modules/tgui/states/always.dm index b6c689d5d8..a741e2e3d4 100644 --- a/code/modules/tgui/states/always.dm +++ b/code/modules/tgui/states/always.dm @@ -1,9 +1,8 @@ - - /** - * tgui state: always_state - * - * Always grants the user UI_INTERACTIVE. Period. - **/ +/** + * tgui state: always_state + * + * Always grants the user UI_INTERACTIVE. Period. + */ GLOBAL_DATUM_INIT(always_state, /datum/ui_state/always_state, new) diff --git a/code/modules/tgui/states/conscious.dm b/code/modules/tgui/states/conscious.dm index 4323c1391c..4e2793d130 100644 --- a/code/modules/tgui/states/conscious.dm +++ b/code/modules/tgui/states/conscious.dm @@ -1,8 +1,8 @@ - /** - * tgui state: conscious_state - * - * Only checks if the user is conscious. - **/ +/** + * tgui state: conscious_state + * + * Only checks if the user is conscious. + */ GLOBAL_DATUM_INIT(conscious_state, /datum/ui_state/conscious_state, new) diff --git a/code/modules/tgui/states/contained.dm b/code/modules/tgui/states/contained.dm index 7387f7e6cb..f02424d01e 100644 --- a/code/modules/tgui/states/contained.dm +++ b/code/modules/tgui/states/contained.dm @@ -1,8 +1,8 @@ - /** - * tgui state: contained_state - * - * Checks that the user is inside the src_object. - **/ +/** + * tgui state: contained_state + * + * Checks that the user is inside the src_object. + */ GLOBAL_DATUM_INIT(contained_state, /datum/ui_state/contained_state, new) diff --git a/code/modules/tgui/states/deep_inventory.dm b/code/modules/tgui/states/deep_inventory.dm index 06bdb92f3a..43758cbab1 100644 --- a/code/modules/tgui/states/deep_inventory.dm +++ b/code/modules/tgui/states/deep_inventory.dm @@ -1,8 +1,8 @@ - /** - * tgui state: deep_inventory_state - * - * Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory. - **/ +/** + * tgui state: deep_inventory_state + * + * Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory. + */ GLOBAL_DATUM_INIT(deep_inventory_state, /datum/ui_state/deep_inventory_state, new) diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index c6741f20b8..6bb159640e 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -1,8 +1,8 @@ - /** - * tgui state: default_state - * - * Checks a number of things -- mostly physical distance for humans and view for robots. - **/ +/** + * tgui state: default_state + * + * Checks a number of things -- mostly physical distance for humans and view for robots. + */ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new) diff --git a/code/modules/tgui/states/hands.dm b/code/modules/tgui/states/hands.dm index 5da0e5d500..aedae477dd 100644 --- a/code/modules/tgui/states/hands.dm +++ b/code/modules/tgui/states/hands.dm @@ -1,8 +1,8 @@ - /** - * tgui state: hands_state - * - * Checks that the src_object is in the user's hands. - **/ +/** + * tgui state: hands_state + * + * Checks that the src_object is in the user's hands. + */ GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new) diff --git a/code/modules/tgui/states/human_adjacent.dm b/code/modules/tgui/states/human_adjacent.dm index 0ab20b36ff..7aefe43e44 100644 --- a/code/modules/tgui/states/human_adjacent.dm +++ b/code/modules/tgui/states/human_adjacent.dm @@ -1,10 +1,9 @@ - - /** - * tgui state: human_adjacent_state - * - * In addition to default checks, only allows interaction for a - * human adjacent user. - **/ +/** + * tgui state: human_adjacent_state + * + * In addition to default checks, only allows interaction for a + * human adjacent user. + */ GLOBAL_DATUM_INIT(human_adjacent_state, /datum/ui_state/human_adjacent_state, new) diff --git a/code/modules/tgui/states/inventory.dm b/code/modules/tgui/states/inventory.dm index b8b1ad3b6a..43fe2cb451 100644 --- a/code/modules/tgui/states/inventory.dm +++ b/code/modules/tgui/states/inventory.dm @@ -1,8 +1,8 @@ - /** - * tgui state: inventory_state - * - * Checks that the src_object is in the user's top-level (hand, ear, pocket, belt, etc) inventory. - **/ +/** + * tgui state: inventory_state + * + * Checks that the src_object is in the user's top-level (hand, ear, pocket, belt, etc) inventory. + */ GLOBAL_DATUM_INIT(inventory_state, /datum/ui_state/inventory_state, new) diff --git a/code/modules/tgui/states/language_menu.dm b/code/modules/tgui/states/language_menu.dm index fedc4320e4..5c816c8922 100644 --- a/code/modules/tgui/states/language_menu.dm +++ b/code/modules/tgui/states/language_menu.dm @@ -1,6 +1,6 @@ - /** - * tgui state: language_menu_state - */ +/** + * tgui state: language_menu_state + */ GLOBAL_DATUM_INIT(language_menu_state, /datum/ui_state/language_menu, new) diff --git a/code/modules/tgui/states/not_incapacitated.dm b/code/modules/tgui/states/not_incapacitated.dm index 12fe266bc5..364b59424d 100644 --- a/code/modules/tgui/states/not_incapacitated.dm +++ b/code/modules/tgui/states/not_incapacitated.dm @@ -1,16 +1,16 @@ - /** - * tgui state: not_incapacitated_state - * - * Checks that the user isn't incapacitated - **/ +/** + * tgui state: not_incapacitated_state + * + * Checks that the user isn't incapacitated + */ GLOBAL_DATUM_INIT(not_incapacitated_state, /datum/ui_state/not_incapacitated_state, new) - /** - * tgui state: not_incapacitated_turf_state - * - * Checks that the user isn't incapacitated and that their loc is a turf - **/ +/** + * tgui state: not_incapacitated_turf_state + * + * Checks that the user isn't incapacitated and that their loc is a turf + */ GLOBAL_DATUM_INIT(not_incapacitated_turf_state, /datum/ui_state/not_incapacitated_state, new(no_turfs = TRUE)) diff --git a/code/modules/tgui/states/notcontained.dm b/code/modules/tgui/states/notcontained.dm index 77a7fe01b0..642c6ce95f 100644 --- a/code/modules/tgui/states/notcontained.dm +++ b/code/modules/tgui/states/notcontained.dm @@ -1,8 +1,8 @@ - /** - * tgui state: notcontained_state - * - * Checks that the user is not inside src_object, and then makes the default checks. - **/ +/** + * tgui state: notcontained_state + * + * Checks that the user is not inside src_object, and then makes the default checks. + */ GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new) diff --git a/code/modules/tgui/states/observer.dm b/code/modules/tgui/states/observer.dm index ade0ce66bb..86ad776b13 100644 --- a/code/modules/tgui/states/observer.dm +++ b/code/modules/tgui/states/observer.dm @@ -1,8 +1,8 @@ - /** - * tgui state: observer_state - * - * Checks that the user is an observer/ghost. - **/ +/** + * tgui state: observer_state + * + * Checks that the user is an observer/ghost. + */ GLOBAL_DATUM_INIT(observer_state, /datum/ui_state/observer_state, new) diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm index a4cea7c7c2..88c8a291aa 100644 --- a/code/modules/tgui/states/physical.dm +++ b/code/modules/tgui/states/physical.dm @@ -1,8 +1,8 @@ - /** - * tgui state: physical_state - * - * Short-circuits the default state to only check physical distance. - **/ +/** + * tgui state: physical_state + * + * Short-circuits the default state to only check physical distance. + */ GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new) @@ -22,3 +22,29 @@ GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new) /mob/living/silicon/ai/physical_can_use_topic(src_object) return UI_UPDATE // AIs are not physical. + + +/** + * tgui state: physical_obscured_state + * + * Short-circuits the default state to only check physical distance, being in view doesn't matter + */ + +GLOBAL_DATUM_INIT(physical_obscured_state, /datum/ui_state/physical_obscured_state, new) + +/datum/ui_state/physical_obscured_state/can_use_topic(src_object, mob/user) + . = user.shared_ui_interaction(src_object) + if(. > UI_CLOSE) + return min(., user.physical_obscured_can_use_topic(src_object)) + +/mob/proc/physical_obscured_can_use_topic(src_object) + return UI_CLOSE + +/mob/living/physical_obscured_can_use_topic(src_object) + return shared_living_ui_distance(src_object, viewcheck = FALSE) + +/mob/living/silicon/physical_obscured_can_use_topic(src_object) + return max(UI_UPDATE, shared_living_ui_distance(src_object, viewcheck = FALSE)) // Silicons can always see. + +/mob/living/silicon/ai/physical_obscured_can_use_topic(src_object) + return UI_UPDATE // AIs are not physical. diff --git a/code/modules/tgui/states/self.dm b/code/modules/tgui/states/self.dm index 10849772c6..b0c9500fbc 100644 --- a/code/modules/tgui/states/self.dm +++ b/code/modules/tgui/states/self.dm @@ -1,8 +1,8 @@ - /** - * tgui state: self_state - * - * Only checks that the user and src_object are the same. - **/ +/** + * tgui state: self_state + * + * Only checks that the user and src_object are the same. + */ GLOBAL_DATUM_INIT(self_state, /datum/ui_state/self_state, new) diff --git a/code/modules/tgui/states/zlevel.dm b/code/modules/tgui/states/zlevel.dm index 6ccfd0fe7d..5e3ccfb7de 100644 --- a/code/modules/tgui/states/zlevel.dm +++ b/code/modules/tgui/states/zlevel.dm @@ -1,8 +1,8 @@ - /** - * tgui state: z_state - * - * Only checks that the Z-level of the user and src_object are the same. - **/ +/** + * tgui state: z_state + * + * Only checks that the Z-level of the user and src_object are the same. + */ GLOBAL_DATUM_INIT(z_state, /datum/ui_state/z_state, new) diff --git a/code/modules/tgui/subsystem.dm b/code/modules/tgui/subsystem.dm index 90a00fb607..cbe94e2d7f 100644 --- a/code/modules/tgui/subsystem.dm +++ b/code/modules/tgui/subsystem.dm @@ -1,22 +1,22 @@ - /** - * tgui subsystem - * - * Contains all tgui state and subsystem code. - **/ +/** + * tgui subsystem + * + * Contains all tgui state and subsystem code. + */ - /** - * public - * - * Get a open UI given a user, src_object, and ui_key and try to update it with data. - * - * required user mob The mob who opened/is using the UI. - * required src_object datum The object/datum which owns the UI. - * required ui_key string The ui_key of the UI. - * optional ui datum/tgui The UI to be updated, if it exists. - * optional force_open bool If the UI should be re-opened instead of updated. - * - * return datum/tgui The found UI. - **/ +/** + * public + * + * Get a open UI given a user, src_object, and ui_key and try to update it with data. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object/datum which owns the UI. + * required ui_key string The ui_key of the UI. + * optional ui datum/tgui The UI to be updated, if it exists. + * optional force_open bool If the UI should be re-opened instead of updated. + * + * return datum/tgui The found UI. + */ /datum/controller/subsystem/tgui/proc/try_update_ui(mob/user, datum/src_object, ui_key, datum/tgui/ui, force_open = FALSE) if(isnull(ui)) // No UI was passed, so look for one. ui = get_open_ui(user, src_object, ui_key) @@ -31,17 +31,17 @@ else return null // We couldn't find a UI. - /** - * private - * - * Get a open UI given a user, src_object, and ui_key. - * - * required user mob The mob who opened/is using the UI. - * required src_object datum The object/datum which owns the UI. - * required ui_key string The ui_key of the UI. - * - * return datum/tgui The found UI. - **/ +/** + * private + * + * Get a open UI given a user, src_object, and ui_key. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object/datum which owns the UI. + * required ui_key string The ui_key of the UI. + * + * return datum/tgui The found UI. + */ /datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object, ui_key) var/src_object_key = "[REF(src_object)]" if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list)) @@ -55,15 +55,15 @@ return null // Couldn't find a UI! - /** - * private - * - * Update all UIs attached to src_object. - * - * required src_object datum The object/datum which owns the UIs. - * - * return int The number of UIs updated. - **/ +/** + * private + * + * Update all UIs attached to src_object. + * + * required src_object datum The object/datum which owns the UIs. + * + * return int The number of UIs updated. + */ /datum/controller/subsystem/tgui/proc/update_uis(datum/src_object) var/src_object_key = "[REF(src_object)]" if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list)) @@ -77,15 +77,15 @@ update_count++ // Count each UI we update. return update_count - /** - * private - * - * Close all UIs attached to src_object. - * - * required src_object datum The object/datum which owns the UIs. - * - * return int The number of UIs closed. - **/ +/** + * private + * + * Close all UIs attached to src_object. + * + * required src_object datum The object/datum which owns the UIs. + * + * return int The number of UIs closed. + */ /datum/controller/subsystem/tgui/proc/close_uis(datum/src_object) var/src_object_key = "[REF(src_object)]" if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list)) @@ -99,13 +99,13 @@ close_count++ // Count each UI we close. return close_count - /** - * private - * - * Close *ALL* UIs - * - * return int The number of UIs closed. - **/ +/** + * private + * + * Close *ALL* UIs + * + * return int The number of UIs closed. + */ /datum/controller/subsystem/tgui/proc/close_all_uis() var/close_count = 0 for(var/src_object_key in open_uis) @@ -116,17 +116,17 @@ close_count++ // Count each UI we close. return close_count - /** - * private - * - * Update all UIs belonging to a user. - * - * required user mob The mob who opened/is using the UI. - * optional src_object datum If provided, only update UIs belonging this src_object. - * optional ui_key string If provided, only update UIs with this UI key. - * - * return int The number of UIs updated. - **/ +/** + * private + * + * Update all UIs belonging to a user. + * + * required user mob The mob who opened/is using the UI. + * optional src_object datum If provided, only update UIs belonging this src_object. + * optional ui_key string If provided, only update UIs with this UI key. + * + * return int The number of UIs updated. + */ /datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object = null, ui_key = null) if(isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0) return 0 // Couldn't find any UIs for this user. @@ -138,17 +138,17 @@ update_count++ // Count each UI we upadte. return update_count - /** - * private - * - * Close all UIs belonging to a user. - * - * required user mob The mob who opened/is using the UI. - * optional src_object datum If provided, only close UIs belonging this src_object. - * optional ui_key string If provided, only close UIs with this UI key. - * - * return int The number of UIs closed. - **/ +/** + * private + * + * Close all UIs belonging to a user. + * + * required user mob The mob who opened/is using the UI. + * optional src_object datum If provided, only close UIs belonging this src_object. + * optional ui_key string If provided, only close UIs with this UI key. + * + * return int The number of UIs closed. + */ /datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object = null, ui_key = null) if(isnull(user.open_uis) || !istype(user.open_uis, /list) || open_uis.len == 0) return 0 // Couldn't find any UIs for this user. @@ -160,13 +160,13 @@ close_count++ // Count each UI we close. return close_count - /** - * private - * - * Add a UI to the list of open UIs. - * - * required ui datum/tgui The UI to be added. - **/ +/** + * private + * + * Add a UI to the list of open UIs. + * + * required ui datum/tgui The UI to be added. + */ /datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui) var/src_object_key = "[REF(ui.src_object)]" if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list)) @@ -180,15 +180,15 @@ uis |= ui processing_uis |= ui - /** - * private - * - * Remove a UI from the list of open UIs. - * - * required ui datum/tgui The UI to be removed. - * - * return bool If the UI was removed or not. - **/ +/** + * private + * + * Remove a UI from the list of open UIs. + * + * required ui datum/tgui The UI to be removed. + * + * return bool If the UI was removed or not. + */ /datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui) var/src_object_key = "[REF(ui.src_object)]" if(isnull(open_uis[src_object_key]) || !istype(open_uis[src_object_key], /list)) @@ -210,28 +210,28 @@ return 1 // Let the caller know we did it. - /** - * private - * - * Handle client logout, by closing all their UIs. - * - * required user mob The mob which logged out. - * - * return int The number of UIs closed. - **/ +/** + * private + * + * Handle client logout, by closing all their UIs. + * + * required user mob The mob which logged out. + * + * return int The number of UIs closed. + */ /datum/controller/subsystem/tgui/proc/on_logout(mob/user) return close_user_uis(user) - /** - * private - * - * Handle clients switching mobs, by transferring their UIs. - * - * required user source The client's original mob. - * required user target The client's new mob. - * - * return bool If the UIs were transferred. - **/ +/** + * private + * + * Handle clients switching mobs, by transferring their UIs. + * + * required user source The client's original mob. + * required user target The client's new mob. + * + * return bool If the UIs were transferred. + */ /datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target) if(!source || isnull(source.open_uis) || !istype(source.open_uis, /list) || open_uis.len == 0) return 0 // The old mob had no open UIs. diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 55fe8f0bb5..7971a940d4 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -1,12 +1,12 @@ - /** - * tgui - * - * /tg/station user interface library - **/ +/** + * tgui + * + * /tg/station user interface library + */ - /** - * tgui datum (represents a UI). - **/ +/** + * tgui datum (represents a UI). + */ /datum/tgui /// The mob who opened/is using the UI. var/mob/user @@ -22,8 +22,6 @@ var/width = 0 /// The window height var/height = 0 - /// The style to be used for this UI. - var/style = "nanotrasen" /// The interface (template) to be used for this UI. var/interface /// Update the UI every MC tick. @@ -34,6 +32,8 @@ var/list/initial_data /// The static data used to initialize the UI. var/list/initial_static_data + /// Holder for the json string, that is sent during the initial update + var/_initial_update /// The status/visibility of the UI. var/status = UI_INTERACTIVE /// Topic state used to determine status/interactability. @@ -42,34 +42,31 @@ var/datum/tgui/master_ui /// Children of this UI. var/list/datum/tgui/children = list() - var/custom_browser_id = FALSE - var/ui_screen = "home" - /** - * public - * - * Create a new UI. - * - * required user mob The mob who opened/is using the UI. - * required src_object datum The object or datum which owns the UI. - * required ui_key string The ui_key of the UI. - * required interface string The interface used to render the UI. - * optional title string The title of the UI. - * optional width int The window width. - * optional height int The window height. - * optional master_ui datum/tgui The parent UI. - * optional state datum/ui_state The state used to determine status. - * - * return datum/tgui The requested UI. - **/ -/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state, browser_id = null) +/** + * public + * + * Create a new UI. + * + * required user mob The mob who opened/is using the UI. + * required src_object datum The object or datum which owns the UI. + * required ui_key string The ui_key of the UI. + * required interface string The interface used to render the UI. + * optional title string The title of the UI. + * optional width int The window width. + * optional height int The window height. + * optional master_ui datum/tgui The parent UI. + * optional state datum/ui_state The state used to determine status. + * + * return datum/tgui The requested UI. + */ +/datum/tgui/New(mob/user, datum/src_object, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) src.user = user src.src_object = src_object src.ui_key = ui_key - src.window_id = browser_id ? browser_id : "[REF(src_object)]-[ui_key]" // DO NOT replace with \ref here. src_object could potentially be tagged - src.custom_browser_id = browser_id ? TRUE : FALSE - - set_interface(interface) + // DO NOT replace with \ref here. src_object could potentially be tagged + src.window_id = "[REF(src_object)]-[ui_key]" + src.interface = interface if(title) src.title = sanitize(title) @@ -86,11 +83,11 @@ var/datum/asset/assets = get_asset_datum(/datum/asset/group/tgui) assets.send(user) - /** - * public - * - * Open this UI (and initialize it with data). - **/ +/** + * public + * + * Open this UI (and initialize it with data). + */ /datum/tgui/proc/open() if(!user.client) return // Bail if there is no client. @@ -99,18 +96,16 @@ if(status < UI_UPDATE) return // Bail if we're not supposed to open. - var/window_size - if(width && height) // If we have a width and height, use them. - window_size = "size=[width]x[height];" - else - window_size = "" - + // Build window options + var/window_options = "can_minimize=0;auto_format=0;" + // If we have a width and height, use them. + if(width && height) + window_options += "size=[width]x[height];" // Remove titlebar and resize handles for a fancy window - var/have_title_bar if(user.client.prefs.tgui_fancy) - have_title_bar = "titlebar=0;can_resize=0;" + window_options += "titlebar=0;can_resize=0;" else - have_title_bar = "titlebar=1;can_resize=1;" + window_options += "titlebar=1;can_resize=1;" // Generate page html var/html @@ -120,50 +115,54 @@ // Replace template tokens with important UI data // NOTE: Intentional \ref usage; tgui datums can't/shouldn't // be tagged, so this is an effective unwrap - html = replacetextEx(html, "\[ref]", "\ref[src]") - html = replacetextEx(html, "\[style]", style) + html = replacetextEx(html, "\[tgui:ref]", "\ref[src]") // Open the window. - user << browse(html, "window=[window_id];can_minimize=0;auto_format=0;[window_size][have_title_bar]") - if (!custom_browser_id) - // Instruct the client to signal UI when the window is closed. - // NOTE: Intentional \ref usage; tgui datums can't/shouldn't - // be tagged, so this is an effective unwrap - winset(user, window_id, "on-close=\"uiclose \ref[src]\"") + user << browse(html, "window=[window_id];[window_options]") - if(!initial_data) + // Instruct the client to signal UI when the window is closed. + // NOTE: Intentional \ref usage; tgui datums can't/shouldn't + // be tagged, so this is an effective unwrap + winset(user, window_id, "on-close=\"uiclose \ref[src]\"") + + // Pre-fetch initial state while browser is still loading in + // another thread + if(!initial_data) { initial_data = src_object.ui_data(user) - if(!initial_static_data) + } + if(!initial_static_data) { initial_static_data = src_object.ui_static_data(user) + } + _initial_update = url_encode(get_json(initial_data, initial_static_data)) SStgui.on_open(src) - /** - * public - * - * Reinitialize the UI. - * (Possibly with a new interface and/or data). - * - * optional template string The name of the new interface. - * optional data list The new initial data. - **/ +/** + * public + * + * Reinitialize the UI. + * (Possibly with a new interface and/or data). + * + * optional template string The name of the new interface. + * optional data list The new initial data. + */ /datum/tgui/proc/reinitialize(interface, list/data, list/static_data) if(interface) - set_interface(interface) // Set a new interface. + src.interface = interface if(data) initial_data = data if(static_data) initial_static_data = static_data open() - /** - * public - * - * Close the UI, and all its children. - **/ +/** + * public + * + * Close the UI, and all its children. + */ /datum/tgui/proc/close() user << browse(null, "window=[window_id]") // Close the window. - src_object.ui_close() + src_object.ui_close(user) SStgui.on_close(src) for(var/datum/tgui/child in children) // Loop through and close all children. child.close() @@ -172,67 +171,49 @@ master_ui = null qdel(src) - /** - * public - * - * Set the style for this UI. - * - * required style string The new UI style. - **/ -/datum/tgui/proc/set_style(style) - src.style = lowertext(style) - - /** - * public - * - * Set the interface (template) for this UI. - * - * required interface string The new UI interface. - **/ -/datum/tgui/proc/set_interface(interface) - src.interface = lowertext(interface) - - /** - * public - * - * Enable/disable auto-updating of the UI. - * - * required state bool Enable/disable auto-updating. - **/ +/** + * public + * + * Enable/disable auto-updating of the UI. + * + * required state bool Enable/disable auto-updating. + */ /datum/tgui/proc/set_autoupdate(state = TRUE) autoupdate = state - /** - * private - * - * Package the data to send to the UI, as JSON. - * This includes the UI data and config_data. - * - * return string The packaged JSON. - **/ +/** + * private + * + * Package the data to send to the UI, as JSON. + * This includes the UI data and config_data. + * + * return string The packaged JSON. + */ /datum/tgui/proc/get_json(list/data, list/static_data) var/list/json_data = list() json_data["config"] = list( "title" = title, "status" = status, - "screen" = ui_screen, - "style" = style, "interface" = interface, "fancy" = user.client.prefs.tgui_fancy, - "locked" = user.client.prefs.tgui_lock && !custom_browser_id, + "locked" = user.client.prefs.tgui_lock, "observer" = isobserver(user), "window" = window_id, // NOTE: Intentional \ref usage; tgui datums can't/shouldn't // be tagged, so this is an effective unwrap "ref" = "\ref[src]" ) - + if(!isnull(data)) json_data["data"] = data if(!isnull(static_data)) json_data["static_data"] = static_data + // Send shared states + if(src_object.tgui_shared_states) + json_data["shared"] = src_object.tgui_shared_states + // Generate the JSON. var/json = json_encode(json_data) // Strip #255/improper. @@ -240,13 +221,13 @@ json = replacetext(json, "\improper", "") return json - /** - * private - * - * Handle clicks from the UI. - * Call the src_object's ui_act() if status is UI_INTERACTIVE. - * If the src_object's ui_act() returns 1, update all UIs attacked to it. - **/ +/** + * private + * + * Handle clicks from the UI. + * Call the src_object's ui_act() if status is UI_INTERACTIVE. + * If the src_object's ui_act() returns 1, update all UIs attacked to it. + */ /datum/tgui/Topic(href, href_list) if(user != usr) return // Something is not right here. @@ -256,35 +237,47 @@ switch(action) if("tgui:initialize") - user << output(url_encode(get_json(initial_data, initial_static_data)), "[custom_browser_id ? window_id : "[window_id].browser"]:initialize") + user << output(_initial_update, "[window_id].browser:update") initialized = TRUE - if("tgui:view") - if(params["screen"]) - ui_screen = params["screen"] + if("tgui:setSharedState") + // Update the window state. + update_status(push = FALSE) + // Bail if UI is not interactive or usr calling Topic + // is not the UI user. + if(status != UI_INTERACTIVE) + return + var/key = params["key"] + var/value = params["value"] + if(!src_object.tgui_shared_states) + src_object.tgui_shared_states = list() + src_object.tgui_shared_states[key] = value SStgui.update_uis(src_object) + if("tgui:setFancy") + var/value = text2num(params["value"]) + user.client.prefs.tgui_fancy = value if("tgui:log") // Force window to show frills on fatal errors if(params["fatal"]) winset(user, window_id, "titlebar=1;can-resize=1;size=600x600") + log_message(params["log"]) if("tgui:link") user << link(params["url"]) - if("tgui:fancy") - user.client.prefs.tgui_fancy = TRUE - if("tgui:nofrills") - user.client.prefs.tgui_fancy = FALSE else - update_status(push = FALSE) // Update the window state. - if(src_object.ui_act(action, params, src, state)) // Call ui_act() on the src_object. - SStgui.update_uis(src_object) // Update if the object requested it. + // Update the window state. + update_status(push = FALSE) + // Call ui_act() on the src_object. + if(src_object.ui_act(action, params, src, state)) + // Update if the object requested it. + SStgui.update_uis(src_object) - /** - * private - * - * Update the UI. - * Only updates the data if update is true, otherwise only updates the status. - * - * optional force bool If the UI should be forced to update. - **/ +/** + * private + * + * Update the UI. + * Only updates the data if update is true, otherwise only updates the status. + * + * optional force bool If the UI should be forced to update. + */ /datum/tgui/process(force = FALSE) var/datum/host = src_object.ui_host(user) if(!src_object || !host || !user) // If the object or user died (or something else), abort. @@ -296,42 +289,46 @@ else update_status(push = TRUE) // Otherwise only update status. - /** - * private - * - * Push data to an already open UI. - * - * required data list The data to send. - * optional force bool If the update should be sent regardless of state. - **/ +/** + * private + * + * Push data to an already open UI. + * + * required data list The data to send. + * optional force bool If the update should be sent regardless of state. + */ /datum/tgui/proc/push_data(data, static_data, force = FALSE) - update_status(push = FALSE) // Update the window state. + // Update the window state. + update_status(push = FALSE) + // Cannot update UI if it is not set up yet. if(!initialized) - return // Cannot update UI if it is not set up yet. + return + // Cannot update UI, we have no visibility. if(status <= UI_DISABLED && !force) - return // Cannot update UI, we have no visibility. - + return // Send the new JSON to the update() Javascript function. - user << output(url_encode(get_json(data, static_data)), "[custom_browser_id ? window_id : "[window_id].browser"]:update") + user << output( + url_encode(get_json(data, static_data)), + "[window_id].browser:update") - /** - * private - * - * Updates the UI by interacting with the src_object again, which will hopefully - * call try_ui_update on it. - * - * optional force_open bool If force_open should be passed to ui_interact. - **/ +/** + * private + * + * Updates the UI by interacting with the src_object again, which will hopefully + * call try_ui_update on it. + * + * optional force_open bool If force_open should be passed to ui_interact. + */ /datum/tgui/proc/update(force_open = FALSE) src_object.ui_interact(user, ui_key, src, force_open, master_ui, state) - /** - * private - * - * Update the status/visibility of the UI for its user. - * - * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). - **/ +/** + * private + * + * Update the status/visibility of the UI for its user. + * + * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). + */ /datum/tgui/proc/update_status(push = FALSE) var/status = src_object.ui_status(user, state) if(master_ui) @@ -340,25 +337,26 @@ if(status == UI_CLOSE) close() - /** - * private - * - * Set the status/visibility of the UI. - * - * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE). - * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). - **/ +/** + * private + * + * Set the status/visibility of the UI. + * + * required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE). + * optional push bool Push an update to the UI (an update is always sent for UI_DISABLED). + */ /datum/tgui/proc/set_status(status, push = FALSE) - if(src.status != status) // Only update if status has changed. + // Only update if status has changed. + if(src.status != status) if(src.status == UI_DISABLED) src.status = status if(push) update() else src.status = status - if(status == UI_DISABLED || push) // Update if the UI just because disabled, or a push is requested. + // Update if the UI just because disabled, or a push is requested. + if(status == UI_DISABLED || push) push_data(null, force = TRUE) /datum/tgui/proc/log_message(message) log_tgui("[user] ([user.ckey]) using \"[title]\":\n[message]") - diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 4f7502bb02..83151423e5 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -515,7 +515,7 @@ GLOBAL_LIST_EMPTY(vending_products) if(!ui) var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/vending) assets.send(user) - ui = new(user, src, ui_key, "vending", name, 450, 600, master_ui, state) + ui = new(user, src, ui_key, "Vending", ui_key, 450, 600, master_ui, state) ui.open() /obj/machinery/vending/ui_static_data(mob/user) diff --git a/tgstation.dme b/tgstation.dme index 3b3bb74df1..525cf1ed9e 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -426,7 +426,6 @@ #include "code\datums\components\virtual_reality.dm" #include "code\datums\components\wearertargeting.dm" #include "code\datums\components\wet_floor.dm" -#include "code\datums\components\crafting\craft.dm" #include "code\datums\components\crafting\guncrafting.dm" #include "code\datums\components\crafting\recipes.dm" #include "code\datums\components\crafting\glassware\glassware.dm" diff --git a/tgui-next/docs/converting-old-tgui-interfaces.md b/tgui-next/docs/converting-old-tgui-interfaces.md index c92cefca5e..a42724e05c 100644 --- a/tgui-next/docs/converting-old-tgui-interfaces.md +++ b/tgui-next/docs/converting-old-tgui-interfaces.md @@ -86,9 +86,13 @@ Similarly to the previous example, just add a `||` operator to handle the ```jsx {!!data.condition && ( - value + + value + ) || ( - other value + + other value + )} ``` @@ -140,7 +144,9 @@ This ensures that you'll never be reading a null entry by mistake. Substitute `{ If it's an array, you'll want to do this in the template ```jsx {things.map(thing => ( - Thing {thing.number} is here! + + Thing {thing.number} is here! + ))} ``` @@ -155,11 +161,11 @@ This is quite a bit higher concept than ractive's each statements, so feel free Now for objects, there's a genuinely pretty gross syntax here. We apoligize, it's related to ie8 compatibility nonsense. ```jsx -{map((value, key) => { - return ( - Key is {key}, value is {value} - ); -})(fooObject)} +{map((value, key) => ( + + Key is {key}, value is {value} + +))(fooObject)} ``` Again, sorry for this syntax. `fooObject` would be the object being iterated on, value would be the value of the iterated entry on the list, and key would be the key. the naming of value and key isn't important here, but knowing that it goes `value`, `key` in that order is important. @@ -196,7 +202,9 @@ To do a similar thing in JSX, just check if array is empty like this: ```jsx {fooArray.length === 0 && 'fooArray is empty.'} {fooArray.map(foo => ( - Foo is {foo} + + Foo is {foo} + ))} ``` @@ -314,9 +322,11 @@ The equivalent of `ui-button` is `Button` but it works quite a bit differently. becomes -``` +```jsx - - - {data.sheets} - {(data.sheets >= 1) && ( + + + {!data.anchored && ( + Generator not anchored. + )} +
+ + - )} - - - - - - {data.current_heat < 100 ? ( - Nominal - ) : ( - data.current_heat < 200 ? ( - Caution + + + {data.sheets} + {(data.sheets >= 1) && ( + + )} + + + + + + {data.current_heat < 100 ? ( + Nominal ) : ( - DANGER - ) - )} - - -
-
- - - {data.power_output} - - - - - - - - {data.connected ? data.power_available : "Unconnected"} - - - -
- + data.current_heat < 200 ? ( + Caution + ) : ( + DANGER + ) + )} +
+ + +
+ + + {data.power_output} + + + + + + + + {data.connected ? data.power_available : "Unconnected"} + + + +
+ + ); }; diff --git a/tgui-next/packages/tgui/interfaces/Radio.js b/tgui-next/packages/tgui/interfaces/Radio.js index e335cf1cac..68b48d0230 100644 --- a/tgui-next/packages/tgui/interfaces/Radio.js +++ b/tgui-next/packages/tgui/interfaces/Radio.js @@ -3,9 +3,10 @@ import { toFixed } from 'common/math'; import { useBackend } from '../backend'; import { Box, Button, LabeledList, NumberInput, Section } from '../components'; import { RADIO_CHANNELS } from '../constants'; +import { Window } from '../layouts'; -export const Radio = props => { - const { act, data } = useBackend(props); +export const Radio = (props, context) => { + const { act, data } = useBackend(context); const { freqlock, frequency, @@ -25,84 +26,88 @@ export const Radio = props => { status: !!value, }))(data.channels); return ( -
- - - {freqlock && ( - - {toFixed(frequency / 10, 1) + ' kHz'} - - ) || ( - toFixed(value, 1)} - onDrag={(e, value) => act('frequency', { - adjust: (value - frequency / 10), - })} /> - )} - {tunedChannel && ( - - [{tunedChannel.name}] - - )} - - -
+ + ); }; diff --git a/tgui-next/packages/tgui/interfaces/SatelliteControl.js b/tgui-next/packages/tgui/interfaces/SatelliteControl.js index 73a7f1ffbb..26aadee269 100644 --- a/tgui-next/packages/tgui/interfaces/SatelliteControl.js +++ b/tgui-next/packages/tgui/interfaces/SatelliteControl.js @@ -1,45 +1,48 @@ import { useBackend } from '../backend'; -import { Button, LabeledList, ProgressBar, Section, Table, Box } from '../components'; +import { Button, LabeledList, ProgressBar, Section, Box } from '../components'; import { Fragment } from 'inferno'; import { LabeledListItem } from '../components/LabeledList'; +import { Window } from '../layouts'; -export const SatelliteControl = props => { - const { act, data } = useBackend(props); +export const SatelliteControl = (props, context) => { + const { act, data } = useBackend(context); const satellites = data.satellites || []; return ( - - {data.meteor_shield && ( -
- - - + + {data.meteor_shield && ( +
+ + + - - + ranges={{ + good: [1, Infinity], + average: [0.30, 1], + bad: [-Infinity, 0.30], + }} /> + + +
+ )} +
+ + {satellites.map(satellite => ( + act('toggle', { + id: satellite.id, + })} + /> + ))} +
- )} -
- - {satellites.map(satellite => ( - act('toggle', { - id: satellite.id, - })} - /> - ))} - -
- +
+ ); }; diff --git a/tgui-next/packages/tgui/interfaces/ScannerGate.js b/tgui-next/packages/tgui/interfaces/ScannerGate.js index f1e82729bc..e390284d57 100644 --- a/tgui-next/packages/tgui/interfaces/ScannerGate.js +++ b/tgui-next/packages/tgui/interfaces/ScannerGate.js @@ -2,6 +2,7 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, LabeledList, NumberInput, Section } from '../components'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; +import { Window } from '../layouts'; const DISEASE_THEASHOLD_LIST = [ 'Positive', @@ -67,18 +68,18 @@ const TARGET_NUTRITION_LIST = [ }, ]; -export const ScannerGate = props => { - const { state } = props; - const { act, data } = useBackend(props); +export const ScannerGate = (props, context) => { + const { act, data } = useBackend(context); return ( - - act('toggle_lock')} /> - {!data.locked && ( - - )} - + + + act('toggle_lock')} /> + {!data.locked && ( + + )} + + ); }; @@ -117,9 +118,8 @@ const SCANNER_GATE_ROUTES = { }, }; -const ScannerGateControl = props => { - const { state } = props; - const { act, data } = useBackend(props); +const ScannerGateControl = (props, context) => { + const { act, data } = useBackend(context); const { scan_mode } = data; const route = SCANNER_GATE_ROUTES[scan_mode] || SCANNER_GATE_ROUTES.off; @@ -133,13 +133,13 @@ const ScannerGateControl = props => { content="back" onClick={() => act('set_mode', { new_mode: 'Off' })} /> )}> - +
); }; -const ScannerGateOff = props => { - const { act } = useBackend(props); +const ScannerGateOff = (props, context) => { + const { act } = useBackend(context); return ( @@ -172,9 +172,8 @@ const ScannerGateOff = props => { ); }; -const ScannerGateWanted = props => { - const { state } = props; - const { data } = useBackend(props); +const ScannerGateWanted = (props, context) => { + const { data } = useBackend(context); const { reverse } = data; return ( @@ -182,14 +181,13 @@ const ScannerGateWanted = props => { Trigger if the person scanned {reverse ? 'does not have' : 'has'} {' '}any warrants for their arrest. - + ); }; -const ScannerGateGuns = props => { - const { state } = props; - const { data } = useBackend(props); +const ScannerGateGuns = (props, context) => { + const { data } = useBackend(context); const { reverse } = data; return ( @@ -197,14 +195,13 @@ const ScannerGateGuns = props => { Trigger if the person scanned {reverse ? 'does not have' : 'has'} {' '}any guns. - + ); }; -const ScannerGateMindshield = props => { - const { state } = props; - const { data } = useBackend(props); +const ScannerGateMindshield = (props, context) => { + const { data } = useBackend(context); const { reverse } = data; return ( @@ -212,14 +209,13 @@ const ScannerGateMindshield = props => { Trigger if the person scanned {reverse ? 'does not have' : 'has'} {' '}a mindshield. - + ); }; -const ScannerGateDisease = props => { - const { state } = props; - const { act, data } = useBackend(props); +const ScannerGateDisease = (props, context) => { + const { act, data } = useBackend(context); const { reverse, disease_threshold } = data; return ( @@ -238,14 +234,13 @@ const ScannerGateDisease = props => { })} /> ))} - + ); }; -const ScannerGateSpecies = props => { - const { state } = props; - const { act, data } = useBackend(props); +const ScannerGateSpecies = (props, context) => { + const { act, data } = useBackend(context); const { reverse, target_species } = data; const species = TARGET_SPECIES_LIST.find(species => { return species.value === target_species; @@ -270,14 +265,13 @@ const ScannerGateSpecies = props => { })} /> ))} - +
); }; -const ScannerGateNutrition = props => { - const { state } = props; - const { act, data } = useBackend(props); +const ScannerGateNutrition = (props, context) => { + const { act, data } = useBackend(context); const { reverse, target_nutrition } = data; const nutrition = TARGET_NUTRITION_LIST.find(nutrition => { return nutrition.value === target_nutrition; @@ -299,14 +293,13 @@ const ScannerGateNutrition = props => { })} /> ))} - + ); }; -const ScannerGateNanites = props => { - const { state } = props; - const { act, data } = useBackend(props); +const ScannerGateNanites = (props, context) => { + const { act, data } = useBackend(context); const { reverse, nanite_cloud } = data; return ( @@ -329,13 +322,13 @@ const ScannerGateNanites = props => { - + ); }; -const ScannerGateMode = props => { - const { act, data } = useBackend(props); +const ScannerGateMode = (props, context) => { + const { act, data } = useBackend(context); const { reverse } = data; return ( diff --git a/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js b/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js index 1cc361bc95..606a0700aa 100644 --- a/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js +++ b/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js @@ -1,200 +1,242 @@ import { map } from 'common/collections'; import { Fragment } from 'inferno'; -import { useBackend } from '../backend'; -import { Button, LabeledList, Section, Table, Tabs } from '../components'; +import { useBackend, useLocalState } from '../backend'; +import { Button, Flex, LabeledList, Section, Table, Tabs } from '../components'; +import { Window } from '../layouts'; -export const ShuttleManipulator = props => { - const { act, data } = useBackend(props); +export const ShuttleManipulator = (props, context) => { + const [tab, setTab] = useLocalState(context, 'tab', 1); + return ( + + + + setTab(1)}> + Status + + setTab(2)}> + Templates + + setTab(3)}> + Modification + + + {tab === 1 && ( + + )} + {tab === 2 && ( + + )} + {tab === 3 && ( + + )} + + + ); +}; + +export const ShuttleManipulatorStatus = (props, context) => { + const { act, data } = useBackend(context); const shuttles = data.shuttles || []; + return ( +
+ + {shuttles.map(shuttle => ( + + +
+
+ ); +}; + +export const ShuttleManipulatorTemplates = (props, context) => { + const { act, data } = useBackend(context); const templateObject = data.templates || {}; const selected = data.selected || {}; - const existingShuttle = data.existing_shuttle || {}; + const [ + selectedTemplateId, + setSelectedTemplateId, + ] = useLocalState(context, 'templateId', Object.keys(templateObject)[0]); + const actualTemplates = templateObject[selectedTemplateId]?.templates; return ( - - - {() => ( -
- - {shuttles.map(shuttle => ( - - -
-
- )} -
- - {() => ( -
- - {map((template, templateId) => { - const templates = template.templates || []; - return ( - - {templates.map(actualTemplate => { - const isSelected = ( - actualTemplate.shuttle_id === selected.shuttle_id - ); - // Whoever made the structure being sent is an asshole - return ( -
act('select_template', { - shuttle_id: actualTemplate.shuttle_id, - })} /> - )}> - {(!!actualTemplate.description - || !!actualTemplate.admin_notes - ) && ( - - {!!actualTemplate.description && ( - - {actualTemplate.description} - - )} - {!!actualTemplate.admin_notes && ( - - {actualTemplate.admin_notes} - - )} - - )} -
- ); - })} -
- ); - })(templateObject)} -
-
- )} -
- -
- {selected ? ( - +
+ + + + {map((template, templateId) => ( + setSelectedTemplateId(templateId)}> + {template.port_id} + + ))(templateObject)} + + + + {actualTemplates.map(actualTemplate => { + const isSelected = ( + actualTemplate.shuttle_id === selected.shuttle_id + ); + // Whoever made the structure being sent is an asshole + return (
- {(!!selected.description || !!selected.admin_notes) && ( + key={actualTemplate.shuttle_id} + buttons={( +
- {existingShuttle ? ( -
- - act('jump_to', { - type: 'mobile', - id: existingShuttle.id, - })} /> - )}> - {existingShuttle.status} - {!!existingShuttle.timer && ( - - ({existingShuttle.timeleft}) - - )} - - -
- ) : ( -
- )} -
-
- - ) : 'No shuttle selected'} -
- - + ); + })} +
+
+
+ ); +}; + +export const ShuttleManipulatorModification = (props, context) => { + const { act, data } = useBackend(context); + const selected = data.selected || {}; + const existingShuttle = data.existing_shuttle || {}; + return ( +
+ {selected ? ( + +
+ {(!!selected.description || !!selected.admin_notes) && ( + + {!!selected.description && ( + + {selected.description} + + )} + {!!selected.admin_notes && ( + + {selected.admin_notes} + + )} + + )} +
+ {existingShuttle ? ( +
+ + act('jump_to', { + type: 'mobile', + id: existingShuttle.id, + })} /> + )}> + {existingShuttle.status} + {!!existingShuttle.timer && ( + + ({existingShuttle.timeleft}) + + )} + + +
+ ) : ( +
+ )} +
+
+ + ) : 'No shuttle selected'} +
); }; diff --git a/tgui-next/packages/tgui/interfaces/Signaler.js b/tgui-next/packages/tgui/interfaces/Signaler.js index c656613141..bf06e02d1e 100644 --- a/tgui-next/packages/tgui/interfaces/Signaler.js +++ b/tgui-next/packages/tgui/interfaces/Signaler.js @@ -1,9 +1,10 @@ import { Grid, NumberInput, Button, Section } from '../components'; import { useBackend } from '../backend'; import { toFixed } from 'common/math'; +import { Window } from '../layouts'; -export const Signaler = props => { - const { act, data } = useBackend(props); +export const Signaler = (props, context) => { + const { act, data } = useBackend(context); const { code, frequency, @@ -12,74 +13,78 @@ export const Signaler = props => { } = data; return ( -
- - - Frequency: - - - toFixed(value, 1)} - width={13} - onDrag={(e, value) => act('freq', { - freq: value, - })} /> - - -
+ + +
+ + + Frequency: + + + toFixed(value, 1)} + width={13} + onDrag={(e, value) => act('freq', { + freq: value, + })} /> + + +
+
+
); }; diff --git a/tgui-next/packages/tgui/interfaces/SlimeBodySwapper.js b/tgui-next/packages/tgui/interfaces/SlimeBodySwapper.js index 9b41120b42..51fbd8bce3 100644 --- a/tgui-next/packages/tgui/interfaces/SlimeBodySwapper.js +++ b/tgui-next/packages/tgui/interfaces/SlimeBodySwapper.js @@ -1,7 +1,8 @@ import { useBackend } from '../backend'; -import { Section, LabeledList, ProgressBar, Button, BlockQuote, Grid, Box } from '../components'; +import { Section, LabeledList, Button, Box } from '../components'; +import { Window } from '../layouts'; -export const BodyEntry = props => { +export const BodyEntry = (props, context) => { const { body, swapFunc } = props; const statusMap = { @@ -50,21 +51,25 @@ export const BodyEntry = props => { ); }; -export const SlimeBodySwapper = props => { - const { act, data } = useBackend(props); +export const SlimeBodySwapper = (props, context) => { + const { act, data } = useBackend(context); const { bodies = [], } = data; return ( -
- {bodies.map(body => ( - act('swap', { ref: body.ref })} /> - ))} -
+ + +
+ {bodies.map(body => ( + act('swap', { ref: body.ref })} /> + ))} +
+
+
); }; diff --git a/tgui-next/packages/tgui/interfaces/SmartVend.js b/tgui-next/packages/tgui/interfaces/SmartVend.js index d0c13ad259..b835c3996e 100644 --- a/tgui-next/packages/tgui/interfaces/SmartVend.js +++ b/tgui-next/packages/tgui/interfaces/SmartVend.js @@ -1,61 +1,66 @@ import { map } from 'common/collections'; import { useBackend } from '../backend'; import { Button, NoticeBox, Section, Table } from '../components'; +import { Window } from '../layouts'; -export const SmartVend = props => { - const { act, data } = useBackend(props); +export const SmartVend = (props, context) => { + const { act, data } = useBackend(context); return ( -
act('Dry')}> - {data.drying ? 'Stop drying' : 'Dry'} - - )}> - {data.contents.length === 0 && ( - - Unfortunately, this {data.name} is empty. - - ) || ( - - - - Item - - - - {data.verb ? data.verb : 'Dispense'} - - - {map((value, key) => ( - - - {value.name} - - - {value.amount} - - -
- )} -
+ + +
act('Dry')}> + {data.drying ? 'Stop drying' : 'Dry'} + + )}> + {data.contents.length === 0 && ( + + Unfortunately, this {data.name} is empty. + + ) || ( + + + + Item + + + + {data.verb ? data.verb : 'Dispense'} + + + {map((value, key) => ( + + + {value.name} + + + {value.amount} + + +
+ )} +
+
+
); }; diff --git a/tgui-next/packages/tgui/interfaces/SpawnersMenu.js b/tgui-next/packages/tgui/interfaces/SpawnersMenu.js index 27d8b1f9ff..ba5c5d74ec 100644 --- a/tgui-next/packages/tgui/interfaces/SpawnersMenu.js +++ b/tgui-next/packages/tgui/interfaces/SpawnersMenu.js @@ -1,51 +1,56 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, Section } from '../components'; +import { Window } from '../layouts'; -export const SpawnersMenu = props => { - const { act, data } = useBackend(props); +export const SpawnersMenu = (props, context) => { + const { act, data } = useBackend(context); const spawners = data.spawners || []; return ( -
- {spawners.map(spawner => ( -
-
+ ))}
- ))} -
+ + ); }; diff --git a/tgui-next/packages/tgui/interfaces/StationAlertConsole.js b/tgui-next/packages/tgui/interfaces/StationAlertConsole.js index c372b532e9..504467f985 100644 --- a/tgui-next/packages/tgui/interfaces/StationAlertConsole.js +++ b/tgui-next/packages/tgui/interfaces/StationAlertConsole.js @@ -1,9 +1,20 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Section } from '../components'; +import { Window } from '../layouts'; -export const StationAlertConsole = props => { - const { data } = useBackend(props); +export const StationAlertConsole = () => { + return ( + + + + + + ); +}; + +export const StationAlertConsoleContent = (props, context) => { + const { data } = useBackend(context); const categories = data.alarms || []; const fire = categories['Fire'] || []; const atmos = categories['Atmosphere'] || []; diff --git a/tgui-next/packages/tgui/interfaces/SuitStorageUnit.js b/tgui-next/packages/tgui/interfaces/SuitStorageUnit.js index f858505a37..65c990d763 100644 --- a/tgui-next/packages/tgui/interfaces/SuitStorageUnit.js +++ b/tgui-next/packages/tgui/interfaces/SuitStorageUnit.js @@ -1,9 +1,10 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, Icon, LabeledList, NoticeBox, Section } from '../components'; +import { Window } from '../layouts'; -export const SuitStorageUnit = props => { - const { act, data } = useBackend(props); +export const SuitStorageUnit = (props, context) => { + const { act, data } = useBackend(context); const { locked, open, @@ -16,96 +17,98 @@ export const SuitStorageUnit = props => { storage, } = data; return ( - - {!!(occupied && safeties) && ( - - Biological entity detected in suit chamber. Please remove - before continuing with operation. - - )} - {uv_active && ( - - Contents are currently being decontaminated. Please wait. - - ) || ( -
- {!open && ( -
- )} -
+ + + {!!(occupied && safeties) && ( + + Biological entity detected in suit chamber. Please remove + before continuing with operation. + + )} + {uv_active && ( + + Contents are currently being decontaminated. Please wait. + + ) || ( +
+ {!open && ( +
+ )} +
+
); }; diff --git a/tgui-next/packages/tgui/interfaces/TankDispenser.js b/tgui-next/packages/tgui/interfaces/TankDispenser.js index de05a5750b..a11476df2c 100644 --- a/tgui-next/packages/tgui/interfaces/TankDispenser.js +++ b/tgui-next/packages/tgui/interfaces/TankDispenser.js @@ -1,34 +1,39 @@ import { useBackend } from '../backend'; import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; -export const TankDispenser = props => { - const { act, data } = useBackend(props); +export const TankDispenser = (props, context) => { + const { act, data } = useBackend(context); return ( -
- - act('plasma')} /> - )}> - {data.plasma} - - act('oxygen')} /> - )}> - {data.oxygen} - - -
+ + +
+ + act('plasma')} /> + )}> + {data.plasma} + + act('oxygen')} /> + )}> + {data.oxygen} + + +
+
+
); }; diff --git a/tgui-next/packages/tgui/interfaces/ThermoMachine.js b/tgui-next/packages/tgui/interfaces/ThermoMachine.js index acb31e34d7..e79a112c15 100644 --- a/tgui-next/packages/tgui/interfaces/ThermoMachine.js +++ b/tgui-next/packages/tgui/interfaces/ThermoMachine.js @@ -1,77 +1,79 @@ import { toFixed } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { AnimatedNumber, Button, LabeledList, NumberInput, Section } from '../components'; +import { Window } from '../layouts'; -export const ThermoMachine = props => { - const { act, data } = useBackend(props); +export const ThermoMachine = (props, context) => { + const { act, data } = useBackend(context); return ( - -
- - - toFixed(value, 2)} /> - {' K'} - - - toFixed(value, 2)} /> - {' kPa'} - - -
-
act('power')} /> - )}> - - - act('target', { - target: value, - })} /> - - + + +
+ + + toFixed(value, 2)} /> + {' K'} + + + toFixed(value, 2)} /> + {' kPa'} + + +
+
act('target', { - target: data.min, - })} /> -
- + icon={data.on ? 'power-off' : 'times'} + content={data.on ? 'On' : 'Off'} + selected={data.on} + onClick={() => act('power')} /> + )}> + + + act('target', { + target: value, + })} /> + + +
+ + ); }; diff --git a/tgui-next/packages/tgui/interfaces/TurbineComputer.js b/tgui-next/packages/tgui/interfaces/TurbineComputer.js index 4f632cc149..1f85128682 100644 --- a/tgui-next/packages/tgui/interfaces/TurbineComputer.js +++ b/tgui-next/packages/tgui/interfaces/TurbineComputer.js @@ -1,64 +1,69 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; -export const TurbineComputer = props => { - const { act, data } = useBackend(props); +export const TurbineComputer = (props, context) => { + const { act, data } = useBackend(context); const operational = Boolean(data.compressor && !data.compressor_broke && data.turbine && !data.turbine_broke); return ( -
-
+ + +
+
+
+
); }; diff --git a/tgui-next/packages/tgui/interfaces/Uplink.js b/tgui-next/packages/tgui/interfaces/Uplink.js index 4f2b047c9b..4121c37086 100644 --- a/tgui-next/packages/tgui/interfaces/Uplink.js +++ b/tgui-next/packages/tgui/interfaces/Uplink.js @@ -1,186 +1,191 @@ -import { decodeHtmlEntities } from 'common/string'; -import { Component, Fragment } from 'inferno'; -import { act } from '../byond'; -import { Box, Button, Input, Section, Table, Tabs } from '../components'; +import { createSearch, decodeHtmlEntities } from 'common/string'; +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Flex, Input, Section, Table, Tabs, NoticeBox } from '../components'; +import { formatMoney } from '../format'; +import { Window } from '../layouts'; -// It's a class because we need to store state in the form of the current -// hovered item, and current search terms -export class Uplink extends Component { - constructor() { - super(); - this.state = { - hoveredItem: {}, - currentSearch: '', - }; - } +const MAX_SEARCH_RESULTS = 25; - setHoveredItem(hoveredItem) { - this.setState({ - hoveredItem, - }); - } +export const Uplink = (props, context) => { + const { data } = useBackend(context); + const { telecrystals } = data; + return ( + + + + + + ); +}; - setSearchText(currentSearch) { - this.setState({ - currentSearch, - }); - } - - render() { - const { state } = this.props; - const { config, data } = state; - const { ref } = config; - const { - compact_mode, - lockable, - telecrystals, - categories = [], - } = data; - const { hoveredItem, currentSearch } = this.state; - return ( -
0 ? 'good' : 'bad'}> - {telecrystals} TC - - )} - buttons={( - - Search - this.setSearchText(value)} - ml={1} - mr={1} /> -
- ); - } -} - -const ItemList = props => { +export const GenericUplink = (props, context) => { const { - items, - hoveredItem, - telecrystals, - compact, - onBuy, - onBuyMouseOver, - onBuyMouseOut, + currencyAmount = 0, + currencySymbol = 'cr', } = props; + const { act, data } = useBackend(context); + const { + compactMode, + lockable, + categories = [], + } = data; + const [ + searchText, + setSearchText, + ] = useLocalState(context, 'searchText', ''); + const [ + selectedCategory, + setSelectedCategory, + ] = useLocalState(context, 'category', categories[0]?.name); + const testSearch = createSearch(searchText, item => { + return item.name + item.desc; + }); + const items = searchText.length > 0 + // Flatten all categories and apply search to it + && categories + .flatMap(category => category.items || []) + .filter(testSearch) + .filter((item, i) => i < MAX_SEARCH_RESULTS) + // Select a category and show all items in it + || categories + .find(category => category.name === selectedCategory) + ?.items + // If none of that results in a list, return an empty list + || []; + return ( +
0 ? 'good' : 'bad'}> + {formatMoney(currencyAmount)} {currencySymbol} + + )} + buttons={( + + Search + setSearchText(value)} + mx={1} /> +
+ ); +}; + +const ItemList = (props, context) => { + const { + compactMode, + currencyAmount, + currencySymbol, + } = props; + const { act } = useBackend(context); + const [ + hoveredItem, + setHoveredItem, + ] = useLocalState(context, 'hoveredItem', {}); const hoveredCost = hoveredItem && hoveredItem.cost || 0; - if (compact) { + // Append extra hover data to items + const items = props.items.map(item => { + const notSameItem = hoveredItem && hoveredItem.name !== item.name; + const notEnoughHovered = currencyAmount - hoveredCost < item.cost; + const disabledDueToHovered = notSameItem && notEnoughHovered; + const disabled = currencyAmount < item.cost || disabledDueToHovered; + return { + ...item, + disabled, + }; + }); + if (compactMode) { return ( - {items.map(item => { - const notSameItem = hoveredItem && hoveredItem.name !== item.name; - const notEnoughHovered = telecrystals - hoveredCost < item.cost; - const disabledDueToHovered = notSameItem && notEnoughHovered; - return ( - - - {decodeHtmlEntities(item.name)} - - -
); } - return items.map(item => { - const notSameItem = hoveredItem && hoveredItem.name !== item.name; - const notEnoughHovered = telecrystals - hoveredCost < item.cost; - const disabledDueToHovered = notSameItem && notEnoughHovered; - return ( -
onBuyMouseOver(item)} - onmouseout={() => onBuyMouseOut(item)} - onClick={() => onBuy(item)} /> - )}> - {decodeHtmlEntities(item.desc)} -
- ); - }); + return items.map(item => ( +
setHoveredItem(item)} + onmouseout={() => setHoveredItem({})} + onClick={() => act('buy', { + item: item.name, + })} /> + )}> + {decodeHtmlEntities(item.desc)} +
+ )); }; diff --git a/tgui-next/packages/tgui/interfaces/Wires.js b/tgui-next/packages/tgui/interfaces/Wires.js index f33b261738..6e9ac9b160 100644 --- a/tgui-next/packages/tgui/interfaces/Wires.js +++ b/tgui-next/packages/tgui/interfaces/Wires.js @@ -1,59 +1,62 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; -export const Wires = props => { - const { act, data } = useBackend(props); +export const Wires = (props, context) => { + const { act, data } = useBackend(context); const wires = data.wires || []; const statuses = data.status || []; return ( - -
- - {wires.map(wire => ( - -
- {!!statuses.length && ( + +
- {statuses.map(status => ( - - {status} - - ))} + + {wires.map(wire => ( + +
- )} -
+ {!!statuses.length && ( +
+ {statuses.map(status => ( + + {status} + + ))} +
+ )} + + ); }; diff --git a/tgui-next/packages/tgui/interfaces/common/InterfaceLockNoticeBox.js b/tgui-next/packages/tgui/interfaces/common/InterfaceLockNoticeBox.js index 6554e3ff6f..dabc124967 100644 --- a/tgui-next/packages/tgui/interfaces/common/InterfaceLockNoticeBox.js +++ b/tgui-next/packages/tgui/interfaces/common/InterfaceLockNoticeBox.js @@ -1,16 +1,32 @@ +import { useBackend } from '../../backend'; import { Button, Flex, NoticeBox } from '../../components'; -export const InterfaceLockNoticeBox = props => { +/** + * This component by expects the following fields to be returned + * from ui_data: + * + * - siliconUser: boolean + * - locked: boolean + * + * And expects the following ui_act action to be implemented: + * + * - lock - for toggling the lock as a silicon user. + * + * All props can be redefined if you want custom behavior, but + * it's preferred to stick to defaults. + */ +export const InterfaceLockNoticeBox = (props, context) => { + const { act, data } = useBackend(context); const { - siliconUser, - locked, - onLockStatusChange, - accessText, + siliconUser = data.siliconUser, + locked = data.locked, + onLockStatusChange = () => act('lock'), + accessText = 'an ID card', } = props; // For silicon users if (siliconUser) { return ( - + Interface lock status: @@ -19,7 +35,7 @@ export const InterfaceLockNoticeBox = props => {
+ + + ); +}; +``` + +Here are the key variables you get from a `useBackend(context)` function: + +- `config` is part of core tgui. It contains meta-information about the +interface and who uses it, BYOND refs to various objects, and so forth. +You are rarely going to use it, but sometimes it can be used to your +advantage when doing complex UIs. +- `data` is the data returned from `ui_data` and `ui_static_data` procs in +your DM code. Pretty straight forward. + - Note, that javascript doesn't have associative arrays, so when you + return an associative list from DM, it will be available in `data` as a + javascript object instead of an array. You can use it normally + like so: `object.key`, so it's not a problem if it's representing a + data structure, but common `Array` methods, such as `array.map(item => ...)`, + are not available on it. Always prefer returning clean arrays from your + code, since arrays are easier to work with in javascript! +- `act(name, params)` is a function, which you can call to dispatch an action +to your DM code. It will be processed in `ui_act` proc. Action name will be +available in `params["action"]`, mixed together with the rest of parameters +you have passed in `params` object. + +**Let's talk about the syntax.** + +The syntax you're seeing here is called JSX - a very simple extension of the +core javascript language. It's basically a pre-processor, that takes +expressions that look like html, and turns them into function calls. + +Take a look at this example: + +```jsx +
+ You are in {status} condition! +
+``` + +After compiling the code above, this is what it becomes: + +```js +createElement('div', + { className: 'color-' + status }, + 'You are in ', status, ' condition!'); +``` + +It is very important to remember, that JSX is just a javascript expression +made out of `createElement` function calls. Naturally, this allows doing +all sorts of stuff on these expressions, just like you would with anything +else in javascript. + +Take a look at these examples: + +**Render an element inside of another element if `showProgress` is true.** + +This example uses the `&&` operator (the logical AND). It returns +the first operand if it evaluates to `false`, and returns the second operand +if it evaluates to `true`. + +If `showProgress` is `true`, the whole expression evaluates +to a `` element. If `showProgress` is `false`, the whole +expression evaluates to `false`, and `false` is not rendered by React. + +```jsx + + {showProgress && ( + + )} + +``` + +You can also use the `||` operator (the logical OR), which works the same way, +except it will return the second operand on `false` instead of `true`. + +**Loop over the array to map every item to a corresponding React element.** + +`Array.map()` is a method, that calls a function on every item in the array, +and builds a new array based on what was returned by that function. + +```jsx + + {items.map(item => ( + + {item.content} + + ))} + +``` + +If you need more examples of what you can do with React, see the +[interface conversion guide](docs/converting-old-tgui-interfaces.md). + +#### Splitting UIs into smaller, modular components + +You interface will eventually get really, really big. The easiest thing +you can do in this situation, is divide and conquer. Grab a chunk of your +JSX code, and wrap it into a second, smaller React component: + +```jsx +import { useBackend } from '../backend'; +import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const SampleInterface = (props, context) => { + return ( + + + + + + ); +}; + +const HealthStatus = (props, context) => { + const { act, data } = useBackend(context); + const { + user, + } = props; + const { + health, + color, + } = data; + return ( +
+ + + {health} + + + {color} + + +
+ ); +}; +``` + +## Copypasta + +We all do it, even the best of us. If you just want to make a tgui **fast**, +here's what you need (note that you'll probably be forced to clean your shit up +upon code review): + +```dm +/obj/copypasta/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) // Remember to use the appropriate state. + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "copypasta", name, 300, 300, master_ui, state) + ui.open() + +/obj/copypasta/ui_data(mob/user) + var/list/data = list() + data["var"] = var + return data + +/obj/copypasta/ui_act(action, params) + if(..()) + return + switch(action) + if("copypasta") + var/newvar = params["var"] + // A demo of proper input sanitation. + var = CLAMP(newvar, min_val, max_val) + . = TRUE + update_icon() // Not applicable to all objects. +``` + +And the template: + +```jsx +import { useBackend } from '../backend'; +import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const SampleInterface = (props, context) => { + const { act, data } = useBackend(context); + // Extract `health` and `color` variables from the `data` object. + const { + health, + color, + } = data; + return ( + + +
+ + + {health} + + + {color} + + +
+
+
+ ); +}; +``` diff --git a/tgui/package.json b/tgui/package.json index 0e47e25e76..9d2b57cc69 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -1,6 +1,13 @@ { "name": "tgui", +<<<<<<< HEAD "private": true, +======= + "version": "3.0.0", + "workspaces": [ + "packages/*" + ], +>>>>>>> 2185124... Merge pull request #50497 from stylemistake/tgui-3.0 "scripts": { "build": "gulp --min", "watch": "gulp watch" diff --git a/tgui/packages/common/math.js b/tgui/packages/common/math.js new file mode 100644 index 0000000000..cc7a309563 --- /dev/null +++ b/tgui/packages/common/math.js @@ -0,0 +1,84 @@ +/** + * Limits a number to the range between 'min' and 'max'. + */ +export const clamp = (value, min, max) => { + return value < min ? min : value > max ? max : value; +}; + +/** + * Limits a number between 0 and 1. + */ +export const clamp01 = value => { + return value < 0 ? 0 : value > 1 ? 1 : value; +}; + +/** + * Scales a number to fit into the range between min and max. + */ +export const scale = (value, min, max) => { + return (value - min) / (max - min); +}; + +/** + * Robust number rounding. + * + * Adapted from Locutus, see: http://locutus.io/php/math/round/ + * + * @param {number} value + * @param {number} precision + * @return {number} + */ +export const round = (value, precision) => { + if (!value || isNaN(value)) { + return value; + } + // helper variables + let m, f, isHalf, sgn; + // making sure precision is integer + precision |= 0; + m = Math.pow(10, precision); + value *= m; + // sign of the number + sgn = (value > 0) | -(value < 0); + // isHalf = value % 1 === 0.5 * sgn; + isHalf = Math.abs(value % 1) >= 0.4999999999854481; + f = Math.floor(value); + if (isHalf) { + // rounds .5 away from zero + value = f + (sgn > 0); + } + return (isHalf ? value : Math.round(value)) / m; +}; + +/** + * Returns a string representing a number in fixed point notation. + */ +export const toFixed = (value, fractionDigits = 0) => { + return Number(value).toFixed(Math.max(fractionDigits, 0)); +}; + +/** + * Checks whether a value is within the provided range. + * + * Range is an array of two numbers, for example: [0, 15]. + */ +export const inRange = (value, range) => { + return range + && value >= range[0] + && value <= range[1]; +}; + +/** + * Walks over the object with ranges, comparing value against every range, + * and returns the key of the first matching range. + * + * Range is an array of two numbers, for example: [0, 15]. + */ +export const keyOfMatchingRange = (value, ranges) => { + for (let rangeName of Object.keys(ranges)) { + const range = ranges[rangeName]; + if (inRange(value, range)) { + return rangeName; + } + } +}; diff --git a/tgui/packages/common/package.json b/tgui/packages/common/package.json new file mode 100644 index 0000000000..fbd255abab --- /dev/null +++ b/tgui/packages/common/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "name": "common", + "version": "3.0.0", + "type": "module" +} diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js new file mode 100644 index 0000000000..84f3700048 --- /dev/null +++ b/tgui/packages/tgui-dev-server/link/server.js @@ -0,0 +1,127 @@ +import { createLogger, directLog } from 'common/logging.js'; +import http from 'http'; +import { inspect } from 'util'; +import WebSocket from 'ws'; +import { retrace, loadSourceMaps } from './retrace.js'; + +const logger = createLogger('link'); + +const DEBUG = process.argv.includes('--debug'); + +export { loadSourceMaps }; + +export const setupLink = () => { + logger.log('setting up'); + const wss = setupWebSocketLink(); + setupHttpLink(); + return { + wss, + }; +}; + +export const broadcastMessage = (link, msg) => { + const { wss } = link; + const clients = [...wss.clients]; + logger.log(`broadcasting ${msg.type} to ${clients.length} clients`); + for (let client of clients) { + const json = JSON.stringify(msg); + client.send(json); + } +}; + +const deserializeObject = str => { + return JSON.parse(str, (key, value) => { + if (typeof value === 'object' && value !== null) { + if (value.__error__) { + if (!value.stack) { + return value.string; + } + return retrace(value.stack); + } + if (value.__number__) { + return parseFloat(value.__number__); + } + if (value.__undefined__) { + // NOTE: You should not rely on deserialized object's undefined, + // this is purely for inspection purposes. + return { + [inspect.custom]: () => undefined, + }; + } + return value; + } + return value; + }); +}; + +const handleLinkMessage = msg => { + const { type, payload } = msg; + + if (type === 'log') { + const { level, ns, args } = payload; + // Skip debug messages + if (level <= 0 && !DEBUG) { + return; + } + directLog(ns, ...args.map(arg => { + if (typeof arg === 'object') { + return inspect(arg, { + depth: Infinity, + colors: true, + compact: 8, + }); + } + return arg; + })); + return; + } + + logger.log('unhandled message', msg); +}; + +// WebSocket-based client link +const setupWebSocketLink = () => { + const port = 3000; + const wss = new WebSocket.Server({ port }); + + wss.on('connection', ws => { + logger.log('client connected'); + + ws.on('message', json => { + const msg = deserializeObject(json); + handleLinkMessage(msg); + }); + + ws.on('close', () => { + logger.log('client disconnected'); + }); + }); + + logger.log(`listening on port ${port} (WebSocket)`); + return wss; +}; + +// One way HTTP-based client link for IE8 +const setupHttpLink = () => { + const port = 3001; + + const server = http.createServer((req, res) => { + if (req.method === 'POST') { + let body = ''; + req.on('data', chunk => { + body += chunk.toString(); + }); + req.on('end', () => { + const msg = deserializeObject(body); + handleLinkMessage(msg); + res.end(); + }); + return; + } + res.write('Hello'); + res.end(); + }); + + server.listen(port); + logger.log(`listening on port ${port} (HTTP)`); +}; diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json new file mode 100644 index 0000000000..e397fe3e0b --- /dev/null +++ b/tgui/packages/tgui-dev-server/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "tgui-dev-server", + "version": "3.0.0", + "type": "module", + "dependencies": { + "glob": "^7.1.4", + "source-map": "^0.7.3", + "stacktrace-parser": "^0.1.7", + "ws": "^7.1.2" + } +} diff --git a/tgui/packages/tgui/backend.js b/tgui/packages/tgui/backend.js new file mode 100644 index 0000000000..0c35483487 --- /dev/null +++ b/tgui/packages/tgui/backend.js @@ -0,0 +1,185 @@ +/** + * This file provides a clear separation layer between backend updates + * and what state our React app sees. + * + * Sometimes backend can response without a "data" field, but our final + * state will still contain previous "data" because we are merging + * the response with already existing state. + */ + +import { UI_DISABLED, UI_INTERACTIVE } from './constants'; +import { callByond } from './byond'; + +export const backendUpdate = state => ({ + type: 'backend/update', + payload: state, +}); + +export const backendSetSharedState = (key, nextState) => ({ + type: 'backend/setSharedState', + payload: { key, nextState }, +}); + +export const backendReducer = (state, action) => { + const { type, payload } = action; + + if (type === 'backend/update') { + // Merge config + const config = { + ...state.config, + ...payload.config, + }; + // Merge data + const data = { + ...state.data, + ...payload.static_data, + ...payload.data, + }; + // Merge shared states + const shared = { ...state.shared }; + if (payload.shared) { + for (let key of Object.keys(payload.shared)) { + const value = payload.shared[key]; + if (value === '') { + shared[key] = undefined; + } + else { + shared[key] = JSON.parse(value); + } + } + } + // Calculate our own fields + const visible = config.status !== UI_DISABLED; + const interactive = config.status === UI_INTERACTIVE; + // Return new state + return { + ...state, + config, + data, + shared, + visible, + interactive, + }; + } + + if (type === 'backend/setSharedState') { + const { key, nextState } = payload; + return { + ...state, + shared: { + ...state.shared, + [key]: nextState, + }, + }; + } + + return state; +}; + +/** + * @typedef BackendState + * @type {{ + * config: { + * title: string, + * status: number, + * screen: string, + * style: string, + * interface: string, + * fancy: number, + * locked: number, + * observer: number, + * window: string, + * ref: string, + * }, + * data: any, + * visible: boolean, + * interactive: boolean, + * }} + */ + +/** + * A React hook (sort of) for getting tgui state and related functions. + * + * This is supposed to be replaced with a real React Hook, which can only + * be used in functional components. + * + * @return {BackendState & { + * act: (action: string, params?: object) => void, + * }} + */ +export const useBackend = context => { + const { store } = context; + const state = store.getState(); + const ref = state.config.ref; + const act = (action, params = {}) => { + callByond('', { + src: ref, + action, + ...params, + }); + }; + return { ...state, act }; +}; + +/** + * Allocates state on Redux store without sharing it with other clients. + * + * Use it when you want to have a stateful variable in your component + * that persists between renders, but will be forgotten after you close + * the UI. + * + * It is a lot more performant than `setSharedState`. + * + * @param {any} context React context. + * @param {string} key Key which uniquely identifies this state in Redux store. + * @param {any} initialState Initializes your global variable with this value. + */ +export const useLocalState = (context, key, initialState) => { + const { store } = context; + const state = store.getState(); + const sharedStates = state.shared ?? {}; + const sharedState = (key in sharedStates) + ? sharedStates[key] + : initialState; + return [ + sharedState, + nextState => { + store.dispatch(backendSetSharedState(key, nextState)); + }, + ]; +}; + +/** + * Allocates state on Redux store, and **shares** it with other clients + * in the game. + * + * Use it when you want to have a stateful variable in your component + * that persists not only between renders, but also gets pushed to other + * clients that observe this UI. + * + * This makes creation of observable s + * + * @param {any} context React context. + * @param {string} key Key which uniquely identifies this state in Redux store. + * @param {any} initialState Initializes your global variable with this value. + */ +export const useSharedState = (context, key, initialState) => { + const { store } = context; + const state = store.getState(); + const ref = state.config.ref; + const sharedStates = state.shared ?? {}; + const sharedState = (key in sharedStates) + ? sharedStates[key] + : initialState; + return [ + sharedState, + nextState => { + callByond('', { + src: ref, + action: 'tgui:setSharedState', + key, + value: JSON.stringify(nextState) || '', + }); + }, + ]; +}; diff --git a/tgui/packages/tgui/byond.js b/tgui/packages/tgui/byond.js new file mode 100644 index 0000000000..dc4e681bf1 --- /dev/null +++ b/tgui/packages/tgui/byond.js @@ -0,0 +1,86 @@ +// Reference a global Byond object +const { Byond } = window; + +/** + * Version of Trident engine used in Internet Explorer. + * An integer number or `null` if this is not a trident engine. + * + * - IE 8 - Trident 4.0 + * - IE 11 - Trident 7.0 + */ +const tridentVersion = (() => { + const groups = navigator.userAgent.match(/Trident\/(\d+).+?;/i); + if (!groups) { + return null; + } + const majorVersion = groups[1]; + if (!majorVersion) { + return null; + } + return parseInt(majorVersion, 10); +})(); + +/** + * True if browser is an Internet Explorer 8 or lower. + */ +export const IS_IE8 = tridentVersion !== null + && tridentVersion <= 4; + +/** + * Makes a BYOND call. + * + * If path is empty, this will trigger a Topic call. + * You can reference a specific object by setting the "src" parameter. + * + * See: https://secure.byond.com/docs/ref/skinparams.html + */ +export const callByond = (path, params = {}) => { + Byond.call(path, params); +}; + +/** + * A high-level abstraction of BYOND calls. Makes a BYOND call and returns + * a promise, which (if endpoint has a callback parameter) resolves + * with the return value of that call. + */ +export const callByondAsync = (path, params = {}) => { + // Create a callback array if it doesn't exist yet + window.__callbacks__ = window.__callbacks__ || []; + // Create a Promise and push its resolve function into callback array + const callbackIndex = window.__callbacks__.length; + const promise = new Promise(resolve => { + // TODO: Fix a potential memory leak + window.__callbacks__.push(resolve); + }); + // Call BYOND client + Byond.call(path, { + ...params, + callback: `__callbacks__[${callbackIndex}]`, + }); + return promise; +}; + +/** + * Runs a BYOND skin command + * + * See: https://secure.byond.com/docs/ref/skinparams.html + */ +export const runCommand = command => callByond('winset', { command }); + +/** + * Calls 'winget' on a BYOND skin element, retrieving value by the 'key'. + */ +export const winget = async (id, key) => { + const obj = await callByondAsync('winget', { + id, + property: key, + }); + return obj[key]; +}; + +/** + * Calls 'winset' on a BYOND skin element, setting 'key' to 'value'. + */ +export const winset = (id, key, value) => callByond('winset', { + [`${id}.${key}`]: value, +}); diff --git a/tgui/packages/tgui/components/Box.js b/tgui/packages/tgui/components/Box.js new file mode 100644 index 0000000000..ddaef96437 --- /dev/null +++ b/tgui/packages/tgui/components/Box.js @@ -0,0 +1,197 @@ +import { classes, isFalsy, pureComponentHooks } from 'common/react'; +import { createVNode } from 'inferno'; +import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; +import { CSS_COLORS } from '../constants'; + +const UNIT_PX = 6; + +/** + * Coverts our rem-like spacing unit into a CSS unit. + */ +export const unit = value => { + if (typeof value === 'string') { + return value; + } + if (typeof value === 'number') { + return (value * UNIT_PX) + 'px'; + } +}; + +const isColorCode = str => !isColorClass(str); + +const isColorClass = str => typeof str === 'string' + && CSS_COLORS.includes(str); + +const mapRawPropTo = attrName => (style, value) => { + if (!isFalsy(value)) { + style[attrName] = value; + } +}; + +const mapUnitPropTo = attrName => (style, value) => { + if (!isFalsy(value)) { + style[attrName] = unit(value); + } +}; + +const mapBooleanPropTo = (attrName, attrValue) => (style, value) => { + if (!isFalsy(value)) { + style[attrName] = attrValue; + } +}; + +const mapDirectionalUnitPropTo = (attrName, dirs) => (style, value) => { + if (!isFalsy(value)) { + for (let i = 0; i < dirs.length; i++) { + style[attrName + '-' + dirs[i]] = unit(value); + } + } +}; + +const mapColorPropTo = attrName => (style, value) => { + if (isColorCode(value)) { + style[attrName] = value; + } +}; + +const styleMapperByPropName = { + // Direct mapping + position: mapRawPropTo('position'), + overflow: mapRawPropTo('overflow'), + overflowX: mapRawPropTo('overflow-x'), + overflowY: mapRawPropTo('overflow-y'), + top: mapUnitPropTo('top'), + bottom: mapUnitPropTo('bottom'), + left: mapUnitPropTo('left'), + right: mapUnitPropTo('right'), + width: mapUnitPropTo('width'), + minWidth: mapUnitPropTo('min-width'), + maxWidth: mapUnitPropTo('max-width'), + height: mapUnitPropTo('height'), + minHeight: mapUnitPropTo('min-height'), + maxHeight: mapUnitPropTo('max-height'), + fontSize: mapUnitPropTo('font-size'), + fontFamily: mapRawPropTo('font-family'), + lineHeight: mapRawPropTo('line-height'), + opacity: mapRawPropTo('opacity'), + textAlign: mapRawPropTo('text-align'), + verticalAlign: mapRawPropTo('vertical-align'), + // Boolean props + inline: mapBooleanPropTo('display', 'inline-block'), + bold: mapBooleanPropTo('font-weight', 'bold'), + italic: mapBooleanPropTo('font-style', 'italic'), + nowrap: mapBooleanPropTo('white-space', 'nowrap'), + // Margins + m: mapDirectionalUnitPropTo('margin', [ + 'top', 'bottom', 'left', 'right', + ]), + mx: mapDirectionalUnitPropTo('margin', [ + 'left', 'right', + ]), + my: mapDirectionalUnitPropTo('margin', [ + 'top', 'bottom', + ]), + mt: mapUnitPropTo('margin-top'), + mb: mapUnitPropTo('margin-bottom'), + ml: mapUnitPropTo('margin-left'), + mr: mapUnitPropTo('margin-right'), + // Margins + p: mapDirectionalUnitPropTo('padding', [ + 'top', 'bottom', 'left', 'right', + ]), + px: mapDirectionalUnitPropTo('padding', [ + 'left', 'right', + ]), + py: mapDirectionalUnitPropTo('padding', [ + 'top', 'bottom', + ]), + pt: mapUnitPropTo('padding-top'), + pb: mapUnitPropTo('padding-bottom'), + pl: mapUnitPropTo('padding-left'), + pr: mapUnitPropTo('padding-right'), + // Color props + color: mapColorPropTo('color'), + textColor: mapColorPropTo('color'), + backgroundColor: mapColorPropTo('background-color'), + // Utility props + fillPositionedParent: (style, value) => { + if (value) { + style['position'] = 'absolute'; + style['top'] = 0; + style['bottom'] = 0; + style['left'] = 0; + style['right'] = 0; + } + }, +}; + +export const computeBoxProps = props => { + const computedProps = {}; + const computedStyles = {}; + // Compute props + for (let propName of Object.keys(props)) { + if (propName === 'style') { + continue; + } + const propValue = props[propName]; + const mapPropToStyle = styleMapperByPropName[propName]; + if (mapPropToStyle) { + mapPropToStyle(computedStyles, propValue); + } + else { + computedProps[propName] = propValue; + } + } + // Concatenate styles + let style = ''; + for (let attrName of Object.keys(computedStyles)) { + const attrValue = computedStyles[attrName]; + style += attrName + ':' + attrValue + ';'; + } + if (props.style) { + for (let attrName of Object.keys(props.style)) { + const attrValue = props.style[attrName]; + style += attrName + ':' + attrValue + ';'; + } + } + if (style.length > 0) { + computedProps.style = style; + } + return computedProps; +}; + +export const computeBoxClassName = props => { + const color = props.textColor || props.color; + const backgroundColor = props.backgroundColor; + return classes([ + isColorClass(color) && 'color-' + color, + isColorClass(backgroundColor) && 'color-bg-' + backgroundColor, + ]); +}; + +export const Box = props => { + const { + as = 'div', + className, + children, + ...rest + } = props; + // Render props + if (typeof children === 'function') { + return children(computeBoxProps(props)); + } + const computedClassName = typeof className === 'string' + ? className + ' ' + computeBoxClassName(rest) + : computeBoxClassName(rest); + const computedProps = computeBoxProps(rest); + // Render a wrapper element + return createVNode( + VNodeFlags.HtmlElement, + as, + computedClassName, + children, + ChildFlags.UnknownChildren, + computedProps); +}; + +Box.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.js new file mode 100644 index 0000000000..e517245343 --- /dev/null +++ b/tgui/packages/tgui/components/Button.js @@ -0,0 +1,272 @@ +import { classes, pureComponentHooks } from 'common/react'; +import { Component, createRef } from 'inferno'; +import { IS_IE8 } from '../byond'; +import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from '../hotkeys'; +import { refocusLayout } from '../layouts'; +import { createLogger } from '../logging'; +import { Box } from './Box'; +import { Icon } from './Icon'; +import { Tooltip } from './Tooltip'; + +const logger = createLogger('Button'); + +export const Button = props => { + const { + className, + fluid, + icon, + color, + disabled, + selected, + tooltip, + tooltipPosition, + ellipsis, + content, + iconRotation, + iconSpin, + children, + onclick, + onClick, + ...rest + } = props; + const hasContent = !!(content || children); + // A warning about the lowercase onclick + if (onclick) { + logger.warn( + `Lowercase 'onclick' is not supported on Button and lowercase` + + ` prop names are discouraged in general. Please use a camelCase` + + `'onClick' instead and read: ` + + `https://infernojs.org/docs/guides/event-handling`); + } + // IE8: Use a lowercase "onclick" because synthetic events are fucked. + // IE8: Use an "unselectable" prop because "user-select" doesn't work. + return ( + { + refocusLayout(); + if (!disabled && onClick) { + onClick(e); + } + }} + onKeyDown={e => { + const keyCode = window.event ? e.which : e.keyCode; + // Simulate a click when pressing space or enter. + if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) { + e.preventDefault(); + if (!disabled && onClick) { + onClick(e); + } + return; + } + // Refocus layout on pressing escape. + if (keyCode === KEY_ESCAPE) { + e.preventDefault(); + refocusLayout(); + return; + } + }} + {...rest}> + {icon && ( + + )} + {content} + {children} + {tooltip && ( + + )} + + ); +}; + +Button.defaultHooks = pureComponentHooks; + +export const ButtonCheckbox = props => { + const { checked, ...rest } = props; + return ( + + + ))} + + )} + {!condi && ( + setPillAmount(value)} + onCreate={() => act('create', { + type: 'pill', + amount: pillAmount, + volume: 'auto', + })} /> + )} + {!condi && ( + setPatchAmount(value)} + onCreate={() => act('create', { + type: 'patch', + amount: patchAmount, + volume: 'auto', + })} /> + )} + {!condi && ( + setBottleAmount(value)} + onCreate={() => act('create', { + type: 'bottle', + amount: bottleAmount, + volume: 'auto', + })} /> + )} + {!!condi && ( + setPackAmount(value)} + onCreate={() => act('create', { + type: 'condimentPack', + amount: packAmount, + volume: 'auto', + })} /> + )} + {!!condi && ( + setBottleAmount(value)} + onCreate={() => act('create', { + type: 'condimentBottle', + amount: bottleAmount, + volume: 'auto', + })} /> + )} +
+ ); +}; + +const AnalysisResults = (props, context) => { + const { act, data } = useBackend(context); + const { analyzeVars } = data; + return ( +
act('goScreen', { + screen: 'home', + })} /> + )}> + + + {analyzeVars.name} + + + {analyzeVars.state} + + + + {analyzeVars.color} + + + {analyzeVars.description} + + + {analyzeVars.metaRate} u/minute + + + {analyzeVars.overD} + + + {analyzeVars.addicD} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Cryo.js b/tgui/packages/tgui/interfaces/Cryo.js new file mode 100644 index 0000000000..4ec29490c1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cryo.js @@ -0,0 +1,123 @@ +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { AnimatedNumber, Button, LabeledList, ProgressBar, Section } from '../components'; +import { BeakerContents } from './common/BeakerContents'; +import { Window } from '../layouts'; + +const damageTypes = [ + { + label: "Brute", + type: "bruteLoss", + }, + { + label: "Respiratory", + type: "oxyLoss", + }, + { + label: "Toxin", + type: "toxLoss", + }, + { + label: "Burn", + type: "fireLoss", + }, +]; + +export const Cryo = () => { + return ( + + + + + + ); +}; + +const CryoContent = (props, context) => { + const { act, data } = useBackend(context); + return ( + +
+ + + {data.occupant.name || 'No Occupant'} + + {!!data.hasOccupant && ( + + + {data.occupant.stat} + + + + {' K'} + + + 0 ? 'good' : 'average'}> + + + + {(damageTypes.map(damageType => ( + + + + + + )))} + + )} + +
+
+ + + + + + K + + +
+
act('ejectbeaker')} + content="Eject" /> + )}> + +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DecalPainter.js b/tgui/packages/tgui/interfaces/DecalPainter.js new file mode 100644 index 0000000000..36e764bc66 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DecalPainter.js @@ -0,0 +1,63 @@ +import { useBackend } from '../backend'; +import { Button, Section } from '../components'; +import { Window } from '../layouts'; + +export const DecalPainter = (props, context) => { + const { act, data } = useBackend(context); + const decal_list = data.decal_list || []; + const color_list = data.color_list || []; + const dir_list = data.dir_list || []; + return ( + + +
+ {decal_list.map(decal => ( +
+
+ {color_list.map(color => { + return ( +
+
+ {dir_list.map(dir => { + return ( +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DnaConsole.js b/tgui/packages/tgui/interfaces/DnaConsole.js new file mode 100644 index 0000000000..3cca81918d --- /dev/null +++ b/tgui/packages/tgui/interfaces/DnaConsole.js @@ -0,0 +1,1430 @@ +import { filter, uniqBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { classes } from 'common/react'; +import { capitalize } from 'common/string'; +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Box, Button, Collapsible, Dimmer, Divider, Dropdown, Flex, Icon, LabeledList, NumberInput, ProgressBar, Section } from '../components'; +import { Window } from '../layouts'; + +const SUBJECT_CONCIOUS = 0; +const SUBJECT_SOFT_CRIT = 1; +const SUBJECT_UNCONSCIOUS = 2; +const SUBJECT_DEAD = 3; +const SUBJECT_TRANSFORMING = 4; + +const GENES = ['A', 'T', 'C', 'G']; +const GENE_COLORS = { + A: 'green', + T: 'green', + G: 'blue', + C: 'blue', + X: 'grey', +}; + +const CONSOLE_MODE_STORAGE = 'storage'; +const CONSOLE_MODE_SEQUENCER = 'sequencer'; +const CONSOLE_MODE_ENZYMES = 'enzymes'; +const CONSOLE_MODE_INJECTORS = 'injectors'; + +const STORAGE_MODE_CONSOLE = 'console'; +const STORAGE_MODE_DISK = 'disk'; +const STORAGE_MODE_ADVINJ = 'injector'; + +const STORAGE_CONS_SUBMODE_MUTATIONS = 'mutations'; +const STORAGE_CONS_SUBMODE_CHROMOSOMES = 'chromosomes'; +const STORAGE_DISK_SUBMODE_MUTATIONS = 'mutations'; +const STORAGE_DISK_SUBMODE_ENZYMES = 'diskenzymes'; + +const CHROMOSOME_NEVER = 0; +const CHROMOSOME_NONE = 1; +const CHROMOSOME_USED = 2; + +const MUT_NORMAL = 1; +const MUT_EXTRA = 2; +const MUT_OTHER = 3; + +// __DEFINES/DNA.dm - Mutation "Quality" +const POSITIVE = 1; +const NEGATIVE = 2; +const MINOR_NEGATIVE = 4; +const MUT_COLORS = { + 1: 'good', + 2: 'bad', + 4: 'average', +}; + +const RADIATION_STRENGTH_MAX = 15; +const RADIATION_DURATION_MAX = 30; + +/** + * The following predicate tests if two mutations are functionally + * the same on the basis of their metadata. Useful if your intent is + * to prevent "true" duplicates - i.e. mutations with identical metadata. + */ +const isSameMutation = (a, b) => { + return a.Alias === b.Alias + && a.AppliedChromo === b.AppliedChromo; +}; + +export const DnaConsole = (props, context) => { + const { data, act } = useBackend(context); + const { + isPulsingRads, + radPulseSeconds, + } = data; + const { consoleMode } = data.view; + return ( + + {!!isPulsingRads && ( + + + Radiation pulse in progress... + + {radPulseSeconds}s + + )} + + + + {consoleMode === CONSOLE_MODE_STORAGE && ( + + )} + {consoleMode === CONSOLE_MODE_SEQUENCER && ( + + )} + {consoleMode === CONSOLE_MODE_ENZYMES && ( + + )} + + + ); +}; + +const DnaScanner = (props, context) => { + return ( +
+ )}> + +
+ ); +}; + +const DnaScannerButtons = (props, context) => { + const { data, act } = useBackend(context); + const { + hasDelayedAction, + isPulsingRads, + isScannerConnected, + isScrambleReady, + isViableSubject, + scannerLocked, + scannerOpen, + scrambleSeconds, + } = data; + if (!isScannerConnected) { + return ( + + )} + + + ); +}; + +const GenomeSequencer = (props, context) => { + const { mutation } = props; + const { data, act } = useBackend(context); + const { jokerActive } = data.view; + if (!mutation) { + return ( + + No genome selected for sequencing. + + ); + } + if (mutation.Scrambled) { + return ( + + Sequence unreadable due to unpredictable mutation. + + ); + } + // Create gene cycler buttons + const sequence = mutation.Sequence; + const defaultSeq = mutation.DefaultSeq; + const buttons = []; + for (let i = 0; i < sequence.length; i++) { + const gene = sequence.charAt(i); + const button = ( + { + if (e.ctrlKey) { + act('pulse_gene', { + pos: i + 1, + gene: 'X', + alias: mutation.Alias, + }); + return; + } + if (jokerActive) { + act('pulse_gene', { + pos: i + 1, + gene: 'J', + alias: mutation.Alias, + }); + act('set_view', { + jokerActive: '', + }); + return; + } + act('pulse_gene', { + pos: i + 1, + gene: nextGene, + alias: mutation.Alias, + }); + }} /> + ); + buttons.push(button); + } + // Render genome in two rows + const pairs = []; + for (let i = 0; i < buttons.length; i += 2) { + const pair = ( + + {buttons[i]} + + {buttons[i + 1]} + + ); + + if ((i % 8 === 0) && (i !== 0)) { + pairs.push( + , + ); + } + + pairs.push(pair); + } + return ( + + + {pairs} + + + Tip: Ctrl+Click on the gene to set it to X. + Right Click to cycle in reverse. + + + ); +}; + +const DnaConsoleEnzymes = (props, context) => { + const { data, act } = useBackend(context); + const { + isScannerConnected, + stdDevAcc, + stdDevStr, + } = data; + if (!isScannerConnected) { + return ( +
+ DNA Scanner is not connected. +
+ ); + } + return ( + + + + + + + + + + + + + + + ); +}; + +const RadiationEmitterSettings = (props, context) => { + const { data, act } = useBackend(context); + const { + radStrength, + radDuration, + } = data; + return ( +
+ + + act('set_pulse_strength', { + val: value, + })} /> + + + act('set_pulse_duration', { + val: value, + })} /> + + +
+ ); +}; + +const RadiationEmitterProbs = (props, context) => { + const { data } = useBackend(context); + const { + stdDevAcc, + stdDevStr, + } = data; + return ( +
+ + + {stdDevAcc} + + + 68 % + + + 95 % + + +
+ ); +}; + +const RadiationEmitterPulseBoard = (props, context) => { + const { data, act } = useBackend(context); + const { + subjectUNI = [], + } = data; + // Build blocks of buttons of unique enzymes + const blocks = []; + let buffer = []; + for (let i = 0; i < subjectUNI.length; i++) { + const char = subjectUNI.charAt(i); + // Push a button into the buffer + const button = ( + + + + + + + + + + + + ); + } + if (current_target) { + return ( +
+ + +
+ ); + } + if (!destinations.length) { + return ( +
+ No gateway nodes detected. +
+ ); + } + return ( + + {!gateway_status && ( + + Gateway Unpowered + + )} + {destinations.map(dest => ( +
+ {dest.availible && ( + + ) || ( + + + {dest.reason} + + {!!dest.timeout && ( + + Calibrating... + + )} + + )} +
+ ))} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/GravityGenerator.js b/tgui/packages/tgui/interfaces/GravityGenerator.js new file mode 100644 index 0000000000..cf4eaa7bb0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/GravityGenerator.js @@ -0,0 +1,91 @@ +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components'; +import { Window } from '../layouts'; + +export const GravityGenerator = (props, context) => { + const { act, data } = useBackend(context); + const { + charging_state, + operational, + } = data; + return ( + + + {!operational && ( + + No data available + + )} + {!!operational && charging_state !== 0 && ( + + WARNING - Radiation detected + + )} + {!!operational && charging_state === 0 && ( + + No radiation detected + + )} + {!!operational && ( + + )} + + + ); +}; + +const GravityGeneratorContent = (props, context) => { + const { act, data } = useBackend(context); + const { + breaker, + charge_count, + charging_state, + on, + operational, + } = data; + return ( +
+ + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/GulagItemReclaimer.js b/tgui/packages/tgui/interfaces/GulagItemReclaimer.js new file mode 100644 index 0000000000..846b465998 --- /dev/null +++ b/tgui/packages/tgui/interfaces/GulagItemReclaimer.js @@ -0,0 +1,42 @@ +import { useBackend } from '../backend'; +import { Button, NoticeBox, Section, Table } from '../components'; +import { Window } from '../layouts'; + +export const GulagItemReclaimer = (props, context) => { + const { act, data } = useBackend(context); + const { + mobs = [], + } = data; + return ( + + + {mobs.length === 0 && ( + + No stored items + + )} + {mobs.length > 0 && ( +
+ + {mobs.map(mob => ( + + + {mob.name} + + +
+
+ )} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/HypnoChair.js b/tgui/packages/tgui/interfaces/HypnoChair.js new file mode 100644 index 0000000000..58ac1b0a80 --- /dev/null +++ b/tgui/packages/tgui/interfaces/HypnoChair.js @@ -0,0 +1,80 @@ +import { useBackend } from '../backend'; +import { Button, Icon, Input, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const HypnoChair = (props, context) => { + const { act, data } = useBackend(context); + return ( + + +
+ The Enhanced Interrogation Chamber is designed to induce a + deep-rooted trance trigger into the subject. Once the procedure + is complete, by using the implanted trigger phrase, the + authorities are able to ensure immediate and complete obedience + and truthfulness. +
+
+ + + {data.occupant.name ? data.occupant.name : 'No Occupant'} + + {!!data.occupied && ( + + {data.occupant.stat === 0 + ? 'Conscious' + : data.occupant.stat === 1 + ? 'Unconcious' + : 'Dead'} + + )} + +
+
+ + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/InfraredEmitter.js b/tgui/packages/tgui/interfaces/InfraredEmitter.js new file mode 100644 index 0000000000..766f922a79 --- /dev/null +++ b/tgui/packages/tgui/interfaces/InfraredEmitter.js @@ -0,0 +1,35 @@ +import { useBackend } from '../backend'; +import { Button, Section, LabeledList } from '../components'; +import { Window } from '../layouts'; + +export const InfraredEmitter = (props, context) => { + const { act, data } = useBackend(context); + const { + on, + visible, + } = data; + return ( + + +
+ + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/LanguageMenu.js b/tgui/packages/tgui/interfaces/LanguageMenu.js new file mode 100644 index 0000000000..80e0168c42 --- /dev/null +++ b/tgui/packages/tgui/interfaces/LanguageMenu.js @@ -0,0 +1,107 @@ +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Button, LabeledList, Section } from '../components'; +import { Window } from '../layouts'; + +export const LanguageMenu = (props, context) => { + const { act, data } = useBackend(context); + const { + admin_mode, + is_living, + omnitongue, + languages = [], + unknown_languages = [], + } = data; + return ( + + +
+ + {languages.map(language => ( + + {!!is_living && ( +
+ {!!admin_mode && ( +
act('toggle_omnitongue')} /> + )}> + + {unknown_languages.map(language => ( + act('grant_language', { + language_name: language.name, + })} /> + )}> + {language.desc} + {' '} + Key: ,{language.key} + {' '} + {!!language.shadow && '(gained from mob)'} + {' '} + {language.can_speak + ? 'Can speak.' + : 'Cannot speak.' } + + ))} + +
+ )} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/LaunchpadConsole.js b/tgui/packages/tgui/interfaces/LaunchpadConsole.js new file mode 100644 index 0000000000..1fd24e981d --- /dev/null +++ b/tgui/packages/tgui/interfaces/LaunchpadConsole.js @@ -0,0 +1,241 @@ +import { useBackend } from '../backend'; +import { Box, Button, Divider, Flex, Grid, Input, NoticeBox, NumberInput, Section } from '../components'; +import { Window } from '../layouts'; + +const LaunchpadButtonPad = (props, context) => { + const { act } = useBackend(context); + return ( + + + + + + )} +
+ + {programs.map(program => ( + + +
+
+ + + ); +}; diff --git a/tgui/packages/tgui/interfaces/NtosNetDos.js b/tgui/packages/tgui/interfaces/NtosNetDos.js new file mode 100644 index 0000000000..4d1cd5d426 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NtosNetDos.js @@ -0,0 +1,115 @@ +import { Section, Button, LabeledList, Box, NoticeBox } from "../components"; +import { useBackend } from "../backend"; +import { createLogger } from "../logging"; +import { Fragment } from "inferno"; +import { NtosWindow } from "../layouts"; + +export const NtosNetDos = (props, context) => { + return ( + + + + + + ); +}; + +export const NtosNetDosContent = (props, context) => { + const { act, data } = useBackend(context); + + const { + relays = [], + focus, + target, + speed, + overload, + capacity, + error, + } = data; + + if (error) { + return ( + + + {error} + + + ))} +
+ + + +
+ + {categories.map((category, i) => ( + setCategoryName(category.cat_name)}> + {category.cat_name} + + ))} + + {shownCategory?.recipes.map(recipe => ( + act('pipe_type', { + pipe_type: recipe.pipe_index, + category: shownCategory.cat_name, + })} /> + ))} +
+
+ + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/RemoteRobotControl.js b/tgui/packages/tgui/interfaces/RemoteRobotControl.js new file mode 100644 index 0000000000..eb24bf22f8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/RemoteRobotControl.js @@ -0,0 +1,77 @@ +import { decodeHtmlEntities } from 'common/string'; +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Box, Button, NoticeBox, Section, LabeledList } from '../components'; +import { Window } from '../layouts'; + +export const RemoteRobotControl = (props, context) => { + return ( + + + + + + ); +}; + +export const RemoteRobotControlContent = (props, context) => { + const { act, data } = useBackend(context); + const { + robots = [], + } = data; + + if (!robots.length) { + return ( +
+ + No robots detected + +
+ ); + } + + return robots.map(robot => { + return ( +
+
+ ); + }); +}; diff --git a/tgui/packages/tgui/interfaces/RoboticsControlConsole.js b/tgui/packages/tgui/interfaces/RoboticsControlConsole.js new file mode 100644 index 0000000000..e5f4f6f73b --- /dev/null +++ b/tgui/packages/tgui/interfaces/RoboticsControlConsole.js @@ -0,0 +1,165 @@ +import { Fragment } from 'inferno'; +import { useBackend, useSharedState } from '../backend'; +import { Box, Button, LabeledList, NoticeBox, Section, Tabs } from '../components'; +import { Window } from '../layouts'; + +export const RoboticsControlConsole = (props, context) => { + const { act, data } = useBackend(context); + const [tab, setTab] = useSharedState(context, 'tab', 1); + const { + can_hack, + cyborgs = [], + drones = [], + } = data; + return ( + + + + setTab(1)}> + Cyborgs ({cyborgs.length}) + + setTab(2)}> + Drones ({drones.length}) + + + {tab === 1 && ( + + )} + {tab === 2 && ( + + )} + + + ); +}; + +const Cyborgs = (props, context) => { + const { cyborgs, can_hack } = props; + const { act, data } = useBackend(context); + if (!cyborgs.length) { + return ( + + No cyborg units detected within access parameters + + ); + } + return cyborgs.map(cyborg => { + return ( +
+ {!!can_hack && !cyborg.emagged && ( +
+ ); + }); +}; + +const Drones = (props, context) => { + const { drones } = props; + const { act } = useBackend(context); + + if (!drones.length) { + return ( + + No drone units detected within access parameters + + ); + } + + return drones.map(drone => { + return ( +
act('killdrone', { + ref: drone.ref, + })} /> + )}> + + + + {drone.status + ? "Not Responding" + : 'Nominal'} + + + +
+ ); + }); +}; diff --git a/tgui/packages/tgui/interfaces/Roulette.js b/tgui/packages/tgui/interfaces/Roulette.js new file mode 100644 index 0000000000..9134b87458 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Roulette.js @@ -0,0 +1,399 @@ +import { classes } from "common/react"; +import { useBackend } from "../backend"; +import { Box, Button, Grid, NumberInput } from "../components"; +import { Component } from "inferno"; +import { Window } from "../layouts"; + +const getNumberColor = number => { + if (number === 0) { + return 'green'; + } + + const evenRedRanges = [ + [1, 10], + [19, 28], + ]; + let oddRed = true; + + for (let i = 0; i < evenRedRanges.length; i++) { + let range = evenRedRanges[i]; + if (number >= range[0] && number <= range[1]) { + oddRed = false; + break; + } + } + + const isOdd = (number % 2 === 0); + + return (oddRed ? isOdd : !isOdd) ? 'red' : 'black'; +}; + +export const RouletteNumberButton = (props, context) => { + const { number } = props; + const { act } = useBackend(context); + + return ( + + }> + + {capacityPercent >= 100 && 'Fully Charged' + || inputting && 'Charging' + || 'Not Charging'} + + + + + + + }> + + {outputting + ? 'Sending' + : charge > 0 + ? 'Not Sending' + : 'No Charge'} + + + + + +