diff --git a/SQL/migrate/V046__radials.sql b/SQL/migrate/V046__radials.sql new file mode 100644 index 00000000000..05294a2a95e --- /dev/null +++ b/SQL/migrate/V046__radials.sql @@ -0,0 +1,7 @@ +-- +-- Adds support for underwear updates in PR #8710. +-- What else do you think this does? +-- + +ALTER TABLE `ss13_player_preferences` + ADD `tooltip_style` ENUM('Midnight', 'Plasmafire', 'Retro', 'Slimecore', 'Operative', 'Clockwork') NULL DEFAULT 'Midnight'; diff --git a/aurorastation.dme b/aurorastation.dme index 39d019a09fb..66820e30554 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -112,6 +112,8 @@ #include "code\_onclick\hud\movable_screen_objects.dm" #include "code\_onclick\hud\other_mobs.dm" #include "code\_onclick\hud\parallax.dm" +#include "code\_onclick\hud\radial.dm" +#include "code\_onclick\hud\radial_persistent.dm" #include "code\_onclick\hud\robot.dm" #include "code\_onclick\hud\screen_objects.dm" #include "code\_onclick\hud\spell_screen_objects.dm" @@ -2686,6 +2688,7 @@ #include "code\modules\telesci\telepad.dm" #include "code\modules\telesci\telesci_computer.dm" #include "code\modules\tgs\includes.dm" +#include "code\modules\tooltip\tooltip.dm" #include "code\modules\turbolift\turbolift.dm" #include "code\modules\turbolift\turbolift_areas.dm" #include "code\modules\turbolift\turbolift_console.dm" diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index 726cade4711..94edcc52092 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -144,6 +144,12 @@ return picked return null +//Returns the first element from the list and removes it from the list +/proc/popleft(list/L) + if(length(L)) + . = L[1] + L.Cut(1,2) + //Returns the next element in parameter list after first appearance of parameter element. If it is the last element of the list or not present in list, returns first element. /proc/next_in_list(element, list/L) for(var/i=1, i icon + var/list/choices_values = list() //choice_id -> choice + var/list/page_data = list() //list of choices per page + + + var/selected_choice + var/list/obj/screen/elements = list() + var/obj/screen/radial/center/close_button + var/client/current_user + var/atom/anchor + var/image/menu_holder + var/finished = FALSE + var/datum/callback/custom_check_callback + var/next_check = 0 + var/check_delay = DEFAULT_CHECK_DELAY + + var/radius = 32 + var/starting_angle = 0 + var/ending_angle = 360 + var/zone = 360 + var/min_angle = 45 //Defaults are setup for this value, if you want to make the menu more dense these will need changes. + var/max_elements + var/pages = 1 + var/current_page = 1 + + var/hudfix_method = TRUE //TRUE to change anchor to user, FALSE to shift by py_shift + var/py_shift = 0 + var/entry_animation = TRUE + +//If we swap to vis_contens inventory these will need a redo +/datum/radial_menu/proc/check_screen_border(mob/user) + var/atom/movable/AM = anchor + if(!istype(AM) || !AM.screen_loc) + return + if(AM in user.client.screen) + if(hudfix_method) + anchor = user + else + py_shift = 32 + restrict_to_dir(NORTH) //I was going to parse screen loc here but that's more effort than it's worth. + +//Sets defaults +//These assume 45 deg min_angle +/datum/radial_menu/proc/restrict_to_dir(dir) + switch(dir) + if(NORTH) + starting_angle = 270 + ending_angle = 135 + if(SOUTH) + starting_angle = 90 + ending_angle = 315 + if(EAST) + starting_angle = 0 + ending_angle = 225 + if(WEST) + starting_angle = 180 + ending_angle = 45 + +/datum/radial_menu/proc/setup_menu(use_tooltips) + if(ending_angle > starting_angle) + zone = ending_angle - starting_angle + else + zone = 360 - starting_angle + ending_angle + + max_elements = round(zone / min_angle) + var/paged = max_elements < choices.len + if(elements.len < max_elements) + var/elements_to_add = max_elements - elements.len + for(var/i in 1 to elements_to_add) //Create all elements + var/obj/screen/radial/slice/new_element = new /obj/screen/radial/slice + new_element.tooltips = use_tooltips + new_element.parent = src + elements += new_element + + var/page = 1 + page_data = list(null) + var/list/current = list() + var/list/choices_left = choices.Copy() + while(choices_left.len) + if(current.len == max_elements) + page_data[page] = current + page++ + page_data.len++ + current = list() + if(paged && current.len == max_elements - 1) + current += NEXT_PAGE_ID + continue + else + current += popleft(choices_left) + if(paged && current.len < max_elements) + current += NEXT_PAGE_ID + + page_data[page] = current + pages = page + current_page = 1 + update_screen_objects(anim = entry_animation) + +/datum/radial_menu/proc/update_screen_objects(anim = FALSE) + var/list/page_choices = page_data[current_page] + var/angle_per_element = round(zone / page_choices.len) + for(var/i in 1 to elements.len) + var/obj/screen/radial/E = elements[i] + var/angle = Wrap(starting_angle + (i - 1) * angle_per_element,0,360) + if(i > page_choices.len) + HideElement(E) + else + SetElement(E,page_choices[i],angle,anim = anim,anim_order = i) + +/datum/radial_menu/proc/HideElement(obj/screen/radial/slice/E) + E.overlays.Cut() + E.alpha = 0 + E.name = "None" + E.maptext = null + E.mouse_opacity = 0 + E.choice = null + E.next_page = FALSE + +/datum/radial_menu/proc/SetElement(obj/screen/radial/slice/E,choice_id,angle,anim,anim_order) + //Position + var/py = round(cos(angle) * radius) + py_shift + var/px = round(sin(angle) * radius) + if(anim) + var/timing = anim_order * 0.5 + var/matrix/starting = matrix() + starting.Scale(0.1,0.1) + E.transform = starting + var/matrix/TM = matrix() + animate(E,pixel_x = px,pixel_y = py, transform = TM, time = timing) + else + E.pixel_y = py + E.pixel_x = px + + //Visuals + E.alpha = 255 + E.mouse_opacity = 1 + E.overlays.Cut() + if(choice_id == NEXT_PAGE_ID) + E.name = "Next Page" + E.next_page = TRUE + E.overlays.Add("radial_next") + else + if(istext(choices_values[choice_id])) + E.name = choices_values[choice_id] + else + var/atom/movable/AM = choices_values[choice_id] //Movables only + E.name = AM.name + E.choice = choice_id + E.maptext = null + E.next_page = FALSE + if(choices_icons[choice_id]) + E.overlays.Add(choices_icons[choice_id]) + +/datum/radial_menu/New() + close_button = new + close_button.parent = src + +/datum/radial_menu/proc/Reset() + choices.Cut() + choices_icons.Cut() + choices_values.Cut() + current_page = 1 + +/datum/radial_menu/proc/element_chosen(choice_id,mob/user) + selected_choice = choices_values[choice_id] + +/datum/radial_menu/proc/get_next_id() + return "c_[choices.len]" + +/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips) + if(choices.len) + Reset() + for(var/E in new_choices) + var/id = get_next_id() + choices += id + choices_values[id] = E + if(new_choices[E]) + var/I = extract_image(new_choices[E]) + if(I) + choices_icons[id] = I + setup_menu(use_tooltips) + + +/datum/radial_menu/proc/extract_image(E) + var/mutable_appearance/MA = new /mutable_appearance(E) + if(MA) + MA.layer = HUD_LAYER + MA.appearance_flags |= RESET_TRANSFORM + return MA + + +/datum/radial_menu/proc/next_page() + if(pages > 1) + current_page = Wrap(current_page + 1,1,pages+1) + update_screen_objects() + +/datum/radial_menu/proc/show_to(mob/M) + if(current_user) + hide() + if(!M.client || !anchor) + return + current_user = M.client + //Blank + menu_holder = image(icon = 'icons/effects/effects.dmi', loc = anchor, icon_state = "nothing", layer = HUD_LAYER) + menu_holder.appearance_flags |= KEEP_APART + menu_holder.vis_contents += elements + close_button + current_user.images += menu_holder + +/datum/radial_menu/proc/hide() + if(current_user) + current_user.images -= menu_holder + +/datum/radial_menu/proc/wait(atom/user, atom/anchor, require_near = FALSE) + while (current_user && !finished && !selected_choice) + if(require_near && !in_range(anchor, user)) + return + if(custom_check_callback && next_check < world.time) + if(!custom_check_callback.Invoke()) + return + else + next_check = world.time + check_delay + stoplag(1) + +/datum/radial_menu/Destroy() + Reset() + hide() + QDEL_NULL(custom_check_callback) + return ..() + +/* + Presents radial menu to user anchored to anchor (or user if the anchor is currently in users screen) + Choices should be a list where list keys are movables or text used for element names and return value + and list values are movables/icons/images used for element icons +*/ +/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE) + if(!user || !anchor || !length(choices)) + return + if(!uniqueid) + uniqueid = "defmenu_[SOFTREF(user)]_[SOFTREF(anchor)]" + + if(radial_menus[uniqueid]) + if(!no_repeat_close) + var/datum/radial_menu/menu = radial_menus[uniqueid] + menu.finished = TRUE + return + + var/datum/radial_menu/menu = new + radial_menus[uniqueid] = menu + if(radius) + menu.radius = radius + if(istype(custom_check)) + menu.custom_check_callback = custom_check + menu.anchor = anchor + menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud + menu.set_choices(choices, tooltips) + menu.show_to(user) + menu.wait(user, anchor, require_near) + var/answer = menu.selected_choice + qdel(menu) + radial_menus -= uniqueid + return answer + +#define RADIAL_INPUT(user, choices) show_radial_menu(user, user, choices) diff --git a/code/_onclick/hud/radial_persistent.dm b/code/_onclick/hud/radial_persistent.dm new file mode 100644 index 00000000000..3666cc8b7f3 --- /dev/null +++ b/code/_onclick/hud/radial_persistent.dm @@ -0,0 +1,71 @@ +/* + A derivative of radial menu which persists onscreen until closed and invokes a callback each time an element is clicked +*/ + +/obj/screen/radial/persistent/center + name = "Close Menu" + icon_state = "radial_center" + +/obj/screen/radial/persistent/center/Click(location, control, params) + if(usr.client == parent.current_user) + parent.element_chosen(null,usr) + +/obj/screen/radial/persistent/center/MouseEntered(location, control, params) + . = ..() + icon_state = "radial_center_focus" + +/obj/screen/radial/persistent/center/MouseExited(location, control, params) + . = ..() + icon_state = "radial_center" + +/datum/radial_menu/persistent + var/uniqueid + var/datum/callback/select_proc_callback + +/datum/radial_menu/persistent/New() + close_button = new /obj/screen/radial/persistent/center + close_button.parent = src + +/datum/radial_menu/persistent/element_chosen(choice_id,mob/user) + select_proc_callback.Invoke(choices_values[choice_id]) + +/datum/radial_menu/persistent/proc/change_choices(list/newchoices, tooltips) + if(!newchoices.len) + return + Reset() + set_choices(newchoices,tooltips) + +/datum/radial_menu/persistent/Destroy() + QDEL_NULL(select_proc_callback) + radial_menus -= uniqueid + Reset() + hide() + return ..() + +/* + Creates a persistent radial menu and shows it to the user, anchored to anchor (or user if the anchor is currently in users screen). + Choices should be a list where list keys are movables or text used for element names and return value + and list values are movables/icons/images used for element icons + Select_proc is the proc to be called each time an element on the menu is clicked, and should accept the chosen element as its final argument + Clicking the center button will return a choice of null +*/ +/proc/show_radial_menu_persistent(mob/user, atom/anchor, list/choices, datum/callback/select_proc, uniqueid, radius, tooltips = FALSE) + if(!user || !anchor || !length(choices) || !select_proc) + return + if(!uniqueid) + uniqueid = "defmenu_\ref[user]_\ref[anchor]" + + if(radial_menus[uniqueid]) + return + + var/datum/radial_menu/persistent/menu = new + menu.uniqueid = uniqueid + radial_menus[uniqueid] = menu + if(radius) + menu.radius = radius + menu.select_proc_callback = select_proc + menu.anchor = anchor + menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud + menu.set_choices(choices, tooltips) + menu.show_to(user) + return menu diff --git a/code/game/objects/items/weapons/RFD.dm b/code/game/objects/items/weapons/RFD.dm index d5a9a9f0192..6a1b693269d 100644 --- a/code/game/objects/items/weapons/RFD.dm +++ b/code/game/objects/items/weapons/RFD.dm @@ -2,7 +2,7 @@ /obj/item/rfd name = "\improper Rapid-Fabrication-Device" desc = "A device used for rapid fabrication. The matter decompression matrix is untuned, rendering it useless." - icon = 'icons/obj/tools.dmi' + icon = 'icons/obj/rfd.dmi' icon_state = "rfd" item_state = "rfd" opacity = 0 @@ -17,24 +17,17 @@ origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 2) matter = list(DEFAULT_WALL_MATERIAL = 50000) drop_sound = 'sound/items/drop/gun.ogg' - var/datum/effect_system/sparks/spark_system var/stored_matter = 30 // Starts off full. var/working = 0 var/mode = 1 var/number_of_modes = 1 - var/modes = null + var/list/modes var/crafting = FALSE /obj/item/rfd/Initialize() . = ..() - src.spark_system = bind_spark(src, 5) update_icon() -/obj/item/rfd/Destroy() - qdel(spark_system) - spark_system = null - return ..() - /obj/item/rfd/attack() return 0 @@ -48,10 +41,12 @@ /obj/item/rfd/attack_self(mob/user) //Change the mode - if(++mode > number_of_modes) mode = 1 - to_chat(user, "Changed mode to '[modes[mode]]'") + if(++mode > number_of_modes) + mode = 1 + to_chat(user, SPAN_NOTICE("The mode selection dial is now at [modes[mode]].")) playsound(src.loc, 'sound/effects/pop.ogg', 50, 0) - if(prob(20)) src.spark_system.queue() + if(prob(20)) + spark(get_turf(loc), 3, alldirs) /obj/item/rfd/attackby(obj/item/W, mob/user) @@ -127,13 +122,37 @@ RFD Construction-Class /obj/item/rfd/construction name = "\improper Rapid-Fabrication-Device C-Class" desc = "A RFD, modified to construct walls and floors." - modes = list("Floor & Walls","Airlock","Deconstruct") - number_of_modes = 3 + var/list/radial_modes = list() var/canRwall = 0 var/disabled = 0 +/obj/item/rfd/construction/Initialize() + . = ..() + radial_modes = list( + "Floors and Walls" = image(icon = 'icons/mob/screen/radial.dmi', icon_state = "wallfloor"), + "Airlock" = image(icon = 'icons/mob/screen/radial.dmi', icon_state = "airlock"), + "Deconstruct" = image(icon = 'icons/mob/screen/radial.dmi', icon_state = "delete") + ) + +/obj/item/rfd/construction/attack_self(mob/user) + var/current_mode = show_radial_menu(user, src, radial_modes, radius = 42, require_near = TRUE, tooltips = TRUE) + switch(current_mode) + if("Floors and Walls") + mode = 1 + if("Airlock") + mode = 2 + if("Deconstruct") + mode = 3 + else + mode = 1 + to_chat(user, SPAN_NOTICE("You switch the selection dial to \"[current_mode]\".")) + playsound(src.loc, 'sound/effects/pop.ogg', 50, 0) + if(prob(20)) + spark(get_turf(src), 3, alldirs) + /obj/item/rfd/construction/afterattack(atom/A, mob/user, proximity) - if(!proximity) return + if(!proximity) + return if(disabled && !isrobot(user)) return 0 if(istype(get_area(A),/area/shuttle)||istype(get_area(A),/turf/space/transit)) @@ -141,7 +160,7 @@ RFD Construction-Class var/turf/t = get_turf(A) if (isNotStationLevel(t.z)) return 0 - return alter_turf(A,user,(mode == 3)) + return alter_turf(A, user, (mode == 3)) /obj/item/rfd/construction/proc/alter_turf(var/turf/T,var/mob/user,var/deconstruct) @@ -186,14 +205,14 @@ RFD Construction-Class return 0 if(!useResource(build_cost, user)) - user << "The \'Low Ammo\' light on the device blinks yellow." + to_chat(user, SPAN_WARNING("The \'Low Ammo\' light on the device blinks yellow.")) flick("[icon_state]-empty", src) return 0 - playsound(src.loc, 'sound/machines/click.ogg', 50, 1) + playsound(get_turf(src), 'sound/machines/hydraulic_long.ogg', 50, 1) working = 1 - to_chat(user, "[(deconstruct ? "Deconstructing" : "Building")] [build_type]...") + user.visible_message(SPAN_NOTICE("[user] holds \the [src] towards \the [T]."), SPAN_NOTICE("You start [deconstruct ? "deconstructing" : "constructing"] \a [build_type]...")) if(build_delay && !do_after(user, build_delay)) working = 0 @@ -210,7 +229,7 @@ RFD Construction-Class else qdel(T) - playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) + playsound(get_turf(src), 'sound/effects/magnetclamp.ogg', 50, 1) return 1 /obj/item/rfd/construction/borg diff --git a/code/game/objects/items/weapons/tools.dm b/code/game/objects/items/weapons/tools.dm index 7cc7a01fe8c..21d55f490b7 100644 --- a/code/game/objects/items/weapons/tools.dm +++ b/code/game/objects/items/weapons/tools.dm @@ -688,7 +688,7 @@ /obj/item/powerdrill name = "impact wrench" - desc = " The screwdriver's big brother." + desc = "The screwdriver's big brother." icon = 'icons/obj/tools.dmi' item_icons = list( slot_l_hand_str = 'icons/mob/items/lefthand_tools.dmi', @@ -696,23 +696,23 @@ ) icon_state = "powerdrillyellow" item_state = "powerdrillyellow" - var/drillcolor = null force = 8 attack_verb = list("gored", "drilled", "screwed", "punctured") w_class = ITEMSIZE_SMALL toolspeed = 3 usesound = 'sound/items/drill_use.ogg' - + var/drillcolor = null + var/current_tool = 1 var/list/tools = list( "screwdriverbit", "wrenchbit" ) - var/current_tool = 1 + /obj/item/powerdrill/Initialize() . = ..() - switch(pick("red","blue","yellow","green")) + switch(pick("red", "blue", "yellow", "green")) if ("red") drillcolor = "red" if ("blue") diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index dfa4a0d8a81..05ec0b79cdb 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -19,7 +19,8 @@ ///////// //OTHER// ///////// - var/datum/preferences/prefs = null + var/datum/preferences/prefs + var/datum/tooltip/tooltips var/move_delay = 1 var/moving = null var/adminobs = null diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index cb0b54c39ce..f830c1e003e 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -373,10 +373,8 @@ prefs.client = src // Safety reasons here. prefs.last_ip = address //these are gonna be used for banning prefs.last_id = computer_id //these are gonna be used for banning -#if DM_VERSION >= 511 if (byond_version >= 511 && prefs.clientfps) fps = prefs.clientfps -#endif // DM_VERSION >= 511 if(SStheming) SStheming.apply_theme_from_perfs(src) @@ -425,6 +423,9 @@ del(src) return 0 + if(!tooltips) + tooltips = new /datum/tooltip(src) + if(holder) add_admin_verbs() @@ -575,6 +576,10 @@ if(prefs) prefs.ShowChoices(usr) +/client/proc/apply_fps(var/client_fps) + if(world.byond_version >= 511 && byond_version >= 511 && client_fps >= 0 && client_fps <= 1000) + vars["fps"] = prefs.clientfps + //I honestly can't find a good place for this atm. //If the webint interaction gets more features, I'll move it. - Skull132 /client/verb/view_linking_requests() diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm index 68a0dc602ca..d80aa5e437a 100644 --- a/code/modules/client/preference_setup/global/01_ui.dm +++ b/code/modules/client/preference_setup/global/01_ui.dm @@ -30,7 +30,8 @@ "html_UI_style", "skin_theme", "ooccolor", - "clientfps" + "clientfps", + "tooltip_style" ), "args" = list("ckey") ) @@ -49,6 +50,7 @@ "skin_theme", "ooccolor", "clientfps", + "tooltip_style", "ckey" = 1 ) ) @@ -62,7 +64,8 @@ "html_UI_style" = pref.html_UI_style, "skin_theme" = pref.skin_theme, "ooccolor" = pref.ooccolor, - "clientfps" = pref.clientfps + "clientfps" = pref.clientfps, + "tooltip_style" = pref.tooltip_style ) /datum/category_item/player_setup_item/player_global/ui/sanitize_preferences() @@ -81,6 +84,7 @@ dat += "Custom UI (recommended for White UI):
" dat += "-Color: [pref.UI_style_color] [HTML_RECT(pref.UI_style_color)] - reset
" dat += "-Alpha(transparency): [pref.UI_style_alpha] - reset
" + dat += "Tooltip Style: [pref.tooltip_style]
" dat += "HTML UI Style: [pref.html_UI_style]
" dat += "Main UI Style: [pref.skin_theme]
" dat += "-FPS: [pref.clientfps] - reset
" @@ -132,16 +136,27 @@ pref.ooccolor = new_ooccolor return TOPIC_REFRESH - else if ("select_fps") - #if DM_VERSION >= 511 - var/desiredfps = input(user, "Choose your desired fps.\n(0 = synced with server tick rate (currently:[world.fps]))", "Character Preference") as null|num - pref.clientfps = sanitize_integer(desiredfps, 0, 1000, initial(pref.clientfps)) - user.client.fps = pref.clientfps - #else - to_user_chat("\nThis server does not currently support client side fps. You can set now for when it does.") - #endif // DM_VERSION >= 511 + else if(href_list["select_fps"]) + var/version_message + if (user.client && user.client.byond_version < 511) + version_message = "\nYou need to be using byond version 511 or later to take advantage of this feature, your version of [user.client.byond_version] is too low" + if (world.byond_version < 511) + version_message += "\nThis server does not currently support client side fps. You can set now for when it does." + var/new_fps = input(user, "Choose your desired fps.[version_message]\n(0 = synced with server tick rate (currently:[world.fps]))", "Global Preference") as num|null + if (isnum(new_fps) && CanUseTopic(user)) + pref.clientfps = Clamp(new_fps, 0, 1000) + var/mob/target_mob = preference_mob() + if(target_mob && target_mob.client) + target_mob.client.apply_fps(pref.clientfps) + return TOPIC_REFRESH + else if(href_list["select_tooltip_style"]) + var/tooltip_style_new = input(user, "Choose a new tooltip style.", "Global Preference", pref.tooltip_style) as null|anything in all_tooltip_styles + if(!tooltip_style_new || !CanUseTopic(user)) + return TOPIC_NOACTION + pref.tooltip_style = tooltip_style_new + return TOPIC_REFRESH else if(href_list["reset"]) switch(href_list["reset"]) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index fe5ffc2d875..5996f9cc228 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -28,6 +28,8 @@ datum/preferences var/UI_style_alpha = 255 var/html_UI_style = "Nano" var/skin_theme = "Light" + //Style for popup tooltips + var/tooltip_style = "Midnight" var/motd_hash = "" //Hashes for the new server greeting window. var/memo_hash = "" diff --git a/code/modules/client/ui_style.dm b/code/modules/client/ui_style.dm index 6beb7842644..89172ff0705 100644 --- a/code/modules/client/ui_style.dm +++ b/code/modules/client/ui_style.dm @@ -1,6 +1,4 @@ - - -/var/all_ui_styles = list( +var/all_ui_styles = list( "Midnight" = 'icons/mob/screen/midnight.dmi', "Orange" = 'icons/mob/screen/orange.dmi', "old" = 'icons/mob/screen/old.dmi', @@ -8,12 +6,20 @@ "old-noborder" = 'icons/mob/screen/old-noborder.dmi' ) +var/all_tooltip_styles = list( + "Midnight", //Default for everyone is the first one, + "Plasmafire", + "Retro", + "Slimecore", + "Operative", + "Clockwork" + ) + /proc/ui_style2icon(ui_style) if(ui_style in all_ui_styles) return all_ui_styles[ui_style] return all_ui_styles["White"] - /client/verb/change_ui() set name = "Change UI" set category = "Preferences" diff --git a/code/modules/heavy_vehicle/mech_movement.dm b/code/modules/heavy_vehicle/mech_movement.dm index c8205c8b70a..4d9df07a706 100644 --- a/code/modules/heavy_vehicle/mech_movement.dm +++ b/code/modules/heavy_vehicle/mech_movement.dm @@ -68,7 +68,7 @@ if(exosuit.emp_damage >= EMP_MOVE_DISRUPT && prob(30)) failed = TRUE if(failed) - moving_dir = pick(GLOB.cardinal - exosuit.dir) + moving_dir = pick(cardinal - exosuit.dir) exosuit.get_cell().use(exosuit.legs.power_use * CELLRATE) if(exosuit.dir != moving_dir) diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm index 005b7183307..5670dce50d2 100644 --- a/code/modules/mob/living/silicon/robot/login.dm +++ b/code/modules/mob/living/silicon/robot/login.dm @@ -8,4 +8,4 @@ // Forces synths to select an icon relevant to their module if(module && !icon_selected) - choose_icon(icon_selection_tries, module_sprites) \ No newline at end of file + choose_icon() \ No newline at end of file diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index e1b805a3d5d..ff59f00112b 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -12,7 +12,6 @@ var/icontype //Persistent icontype tracking allows for cleaner icon updates var/module_sprites[0] //Used to store the associations between sprite names and sprite index. var/icon_selected = 0 //If icon selection has been completed yet - var/icon_selection_tries = -1//Remaining attempts to select icon before a selection is forced var/spawn_sound = 'sound/voice/liveagain.ogg' var/pitch_toggle = TRUE var/datum/effect/effect/system/ion_trail_follow/ion_trail @@ -1072,39 +1071,37 @@ /mob/living/silicon/robot/proc/choose_icon() set category = "Robot Commands" set name = "Choose Icon" + set waitfor = 0 if(!length(module_sprites)) to_chat(src, SPAN_DANGER("Something is badly wrong with the sprite selection. Harass a coder.")) return + if(icon_selected) verbs -= /mob/living/silicon/robot/proc/choose_icon return - if(icon_selection_tries == -1) - icon_selection_tries = round(module_sprites.len*0.5) - - if(length(module_sprites) == 1 || !client) if(!(icontype in module_sprites)) icontype = module_sprites[1] if(!client) return else - icontype = input(src, "Select an icon! [icon_selection_tries ? "You have [icon_selection_tries] more chance\s." : "This is your last try."]", "Icon Selection") in module_sprites - playsound(get_turf(src), 'sound/effects/pop.ogg', 100, TRUE) - spark(get_turf(src), 5, alldirs) + var/list/options = list() + for(var/i in module_sprites) + var/image/radial_button = image(icon = src.icon, icon_state = module_sprites[i]) + radial_button.overlays.Add(image(icon = src.icon, icon_state = "eyes-[module_sprites[i]]-help")) + options[i] = radial_button + icontype = show_radial_menu(src, src, options, radius = 42, tooltips = TRUE) + + if(!icontype) + return + icon_state = module_sprites[icontype] updateicon() - - if(length(module_sprites) > 1 && icon_selection_tries >= 1 && client) - icon_selection_tries-- - var/choice = input(src, "Look at your icon - is this what you want?", "Icon Confirmation") in list("Yes", "No") - if(choice == "No") - choose_icon() - return - icon_selected = TRUE - icon_selection_tries = 0 + playsound(get_turf(src), 'sound/effects/pop.ogg', 10, TRUE) + spark(get_turf(src), 5, alldirs) verbs -= /mob/living/silicon/robot/proc/choose_icon to_chat(src, SPAN_NOTICE("Your icon has been set. You now require a module reset to change it.")) diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index dd784ded060..4f473bbcbc2 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -65,7 +65,6 @@ var/global/list/robot_modules = list( R.set_module_sprites(sprites) R.icon_selected = FALSE - R.icon_selection_tries = -1 R.choose_icon() for(var/obj/item/I in modules) @@ -81,7 +80,6 @@ var/global/list/robot_modules = list( R.radio.recalculateChannels() R.set_module_sprites(list("Default" = "robot")) R.icon_selected = FALSE - R.icon_selection_tries = -1 R.choose_icon() /obj/item/robot_module/Destroy() diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm new file mode 100644 index 00000000000..4b861173bdb --- /dev/null +++ b/code/modules/tooltip/tooltip.dm @@ -0,0 +1,115 @@ +/* +Tooltips v1.1 - 22/10/15 +Developed by Wire (#goonstation on irc.synirc.net) +- Added support for screen_loc pixel offsets. Should work. Maybe. +- Added init function to more efficiently send base vars +Configuration: +- Set control to the correct skin element (remember to actually place the skin element) +- Set file to the correct path for the .html file (remember to actually place the html file) +- Attach the datum to the user client on login, e.g. + +/client/New() + src.tooltips = new /datum/tooltip(src) + +Usage: +- Define mouse event procs on your (probably HUD) object and simply call the show and hide procs respectively: + +/obj/screen/hud + MouseEntered(location, control, params) + usr.client.tooltip.show(params, title = src.name, content = src.desc) + MouseExited() + usr.client.tooltip.hide() + +Customization: +- Theming can be done by passing the theme var to show() and using css in the html file to change the look +- For your convenience some pre-made themes are included +Notes: +- You may have noticed 90% of the work is done via javascript on the client. Gotta save those cycles man. +- This is entirely untested in any other codebase besides goonstation so I have no idea if it will port nicely. Good luck! + - After testing and discussion (Wire, Remie, MrPerson, AnturK) ToolTips are ok and work for /tg/station13 +*/ + +/datum/tooltip + var/client/owner + var/control = "mainwindow.tooltip" + var/showing = FALSE + var/queueHide = FALSE + var/init = FALSE + +/datum/tooltip/New(client/C) + if(C) + owner = C + show_browser(owner, file2text('code/modules/tooltip/tooltip.html'), "window=[control]") + ..() + +/datum/tooltip/proc/show(atom/movable/thing, params = null, title = null, content = null, theme = "default", special = "none") + if(!thing || !params || (!title && !content) || !owner || !isnum(world.icon_size)) + return FALSE + if(!init) + //Initialize some vars + init = TRUE + send_output(owner, list2params(list(world.icon_size, control)), "[control]:tooltip.init") + + showing = TRUE + + if(title && content) + title = "

[title]

" + content = "

[content]

" + else if(title && !content) + title = "

[title]

" + else if(!title && content) + content = "

[content]

" + + // Strip macros from item names + title = replacetext(title, "\proper", "") + title = replacetext(title, "\improper", "") + + //Make our dumb param object + if(params[1] != "i") //Byond Bug: http://www.byond.com/forum/?post=2352648 + params = "icon-x=16;icon-y=16;[params]" //Put in some placeholders + params = {"{ "cursor": "[params]", "screenLoc": "[thing.screen_loc]" }"} + + //Send stuff to the tooltip + var/view_size = getviewsize(owner.view) + send_output(owner, list2params(list(params, view_size[1] , view_size[2], "[title][content]", theme, special)), "[control]:tooltip.update") + + //ifa hide() was hit while we were showing, run hide() again to avoid stuck tooltips + showing = FALSE + if(queueHide) + hide() + + return TRUE + +/datum/tooltip/proc/hide() + if(queueHide) + addtimer(CALLBACK(src, .proc/do_hide), 1) + else + do_hide() + + queueHide = showing ? TRUE : FALSE + + return TRUE + +/datum/tooltip/proc/do_hide() + winshow(owner, control, FALSE) + +/* TG SPECIFIC CODE */ + +//Open a tooltip for user, at a location based on params +//Theme is a CSS class in tooltip.html, by default this wrapper chooses a CSS class based on the user's UI_style (Midnight, Plasmafire, Retro, etc) +//Includes sanity.checks +/proc/openToolTip(mob/user = null, atom/movable/tip_src = null, params = null, title = "", content = "", theme = "") + if(istype(user)) + if(user.client && user.client.tooltips) + if(!theme && user.client.prefs && user.client.prefs.tooltip_style) + theme = lowertext(user.client.prefs.tooltip_style) + if(!theme) + theme = "midnight" + user.client.tooltips.show(tip_src, params, title, content, theme) + +//Arbitrarily close a user's tooltip +//Includes sanity checks. +/proc/closeToolTip(mob/user) + if(istype(user)) + if(user.client && user.client.tooltips) + user.client.tooltips.hide() \ No newline at end of file diff --git a/code/modules/tooltip/tooltip.html b/code/modules/tooltip/tooltip.html new file mode 100644 index 00000000000..67fb8a77f10 --- /dev/null +++ b/code/modules/tooltip/tooltip.html @@ -0,0 +1,249 @@ + + + + Tooltip + + + + + +
+
+
+ + + + diff --git a/html/changelogs/mattatlas-radials.yml b/html/changelogs/mattatlas-radials.yml new file mode 100644 index 00000000000..0881e964580 --- /dev/null +++ b/html/changelogs/mattatlas-radials.yml @@ -0,0 +1,42 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# wip (For works in progress) +# tweak +# soundadd +# sounddel +# rscadd (general adding of nice things) +# rscdel (general deleting of nice things) +# imageadd +# imagedel +# maptweak +# spellcheck (typo fixes) +# experiment +# balance +# admin +# backend +# security +# refactor +################################# + +# Your name. +author: MattAtlas + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries. +# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Ported radials and tooltips from Nebula and TG." + - rscdel: "Implemented radial menus for borg sprite selection and the RCD." diff --git a/icons/mob/screen/radial.dmi b/icons/mob/screen/radial.dmi new file mode 100644 index 00000000000..d2d032fbbb4 Binary files /dev/null and b/icons/mob/screen/radial.dmi differ diff --git a/icons/obj/rfd.dmi b/icons/obj/rfd.dmi new file mode 100644 index 00000000000..7511b7c1e74 Binary files /dev/null and b/icons/obj/rfd.dmi differ diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi index d92b2471809..8e9f572d641 100644 Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ diff --git a/interface/skin.dmf b/interface/skin.dmf index 1aa6a8aae9a..7796e8d0617 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -1075,6 +1075,15 @@ window "mainwindow" command = ".winset \"saybutton.is-checked=true?input.command=\"!say \\\"\" macrobutton.is-checked=false:input.command=\"" is-flat = true button-type = pushbox + elem "tooltip" + type = BROWSER + pos = 0,0 + size = 999x999 + anchor1 = none + anchor2 = none + background-color = none + is-visible = false + saved-params = "" window "mapwindow" elem "mapwindow"