This commit is contained in:
SandPoot
2023-01-31 16:40:17 -03:00
parent 2d5e163916
commit ed06e686e1
29 changed files with 759 additions and 14 deletions
+130
View File
@@ -0,0 +1,130 @@
# Contextual screentips (and when to not use this folder)
Contextual screentips provide information in the form of text at the top of your screen to inform you of the possibilities of an item. The "contextual" here refers to this being handled entirely through code, what it displays and when is completely up to you.
## The elements (and this folder)
This folder provides several useful shortcuts to be able to handle 95% of situations.
### `/datum/element/contextual_screentip_bare_hands`
This element is used to display screentips **when the user hovers over the object with nothing in their active hand.**
It takes parameters in the form of both non-combat mode and, optionally, combat mode.
Example:
```dm
/obj/machinery/firealarm/Initialize(mapload)
. = ..()
AddElement( \
/datum/element/contextual_screentip_bare_hands, \
lmb_text = "Turn on", \
rmb_text = "Turn off", \
)
```
This will display "LMB: Turn on | RMB: Turn off" when the user hovers over a fire alarm with an empty active hand.
### `/datum/element/contextual_screentip_tools`
This element takes a map of tool behaviors to [context lists](#context-lists). These will be displayed **when the user hovers over the object with an item that has the tool behavior.**
Example:
```dm
/obj/structure/table/Initialize(mapload)
if (!(flags_1 & NODECONSTRUCT_1))
var/static/list/tool_behaviors = list(
TOOL_SCREWDRIVER = list(
SCREENTIP_CONTEXT_RMB = "Disassemble",
),
TOOL_WRENCH = list(
SCREENTIP_CONTEXT_RMB = "Deconstruct",
),
)
AddElement(/datum/element/contextual_screentip_tools, tool_behaviors)
```
This will display "RMB: Deconstruct" when the user hovers over a table with a wrench.
### `/datum/element/contextual_screentip_item_typechecks`
This element takes a map of item typepaths to [context lists](#context-lists). These will be displayed **when the user hovers over the object with the selected item.**
Example:
```dm
/obj/item/restraints/handcuffs/cable/Initialize(mapload)
. = ..()
var/static/list/hovering_item_typechecks = list(
/obj/item/stack/rods = list(
SCREENTIP_CONTEXT_LMB = "Craft wired rod",
),
/obj/item/stack/sheet/iron = list(
SCREENTIP_CONTEXT_LMB = "Craft bola",
),
)
AddElement(/datum/element/contextual_screentip_item_typechecks, hovering_item_typechecks)
```
This will display "LMB: Craft bola" when the user hovers over cable restraints with metal in their hand.
## The basic system (and when to not use this folder)
The basic system acknowledges the following two interactions:
### Self-defining items (Type A)
These are items that are defined by their behavior. These should define their contextual text within themselves, and not in their targets.
- Stun batons (LMB to stun, RMB to harm)
- Syringes (LMB to inject, RMB to draw)
- Health analyzers (LMB to scan for health/wounds [another piece of context], RMB to scans for chemicals)
### Receiving action defining objects (Type B)
These are objects (not necessarily items) that are defined by what happens *to* them. These should define their contextual text within themselves, and not in their operating tools.
- Tables (RMB with wrench to deconstruct)
- Construction objects (LMB with glass to put in screen for computers)
- Carbon copies (RMB to take a copy)
---
Both of these are supported, and can be hooked to through several means.
Note that you **must return `CONTEXTUAL_SCREENTIP_SET` if you change the contextual screentip at all**, otherwise you may not see it.
### `COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET`
This signal is registered on **items**, and receives **the hovering object**, provided in the form of `obj/item/source, list/context, atom/target, mob/living/user`.
### `/atom/proc/register_item_context()`, and `/atom/proc/add_item_context()`
`/atom/proc/add_item_context()` is a proc intended to be overridden to easily create Type-B interactions (ones where atoms are hovered over by items). It receives the exact same arguments as `COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET`: `obj/item/source, list/context, atom/target, mob/living/user`.
In order for your `add_item_context()` method to be run, you **must** call `register_item_context()`.
### `COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM`
This signal is registered on **atoms**, and receives **what the user is hovering with**, provided in the form of `atom/source, list/context, obj/item/held_item, mob/living/user`.
### `/atom/proc/register_context()`, and `/atom/proc/add_context()`
`/atom/proc/add_context()` is a proc intended to be overridden to easily create Type-B interactions (ones where atoms are hovered over by items). It receives the exact same arguments as `COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM`: `atom/source, list/context, obj/item/held_item, mob/living/user`.
In order for your `add_context()` method to be run, you **must** call `register_context()`.
---
When using any of these methods, you will receive a mutable context list.
### Context lists
Context lists are lists with keys mapping from `SCREENTIP_CONTEXT_*` to a string. You can find these keys in `code/__DEFINES/screentips.dm`.
The signals and `add_context()` variants mutate the list directly, while shortcut elements will just have you pass them in directly.
For example:
```dm
context[SCREENTIP_CONTEXT_LMB] = "Open"
context[SCREENTIP_CONTEXT_RMB] = "Destroy"
```
@@ -0,0 +1,25 @@
/// Create a "Type-B" contextual screentip interaction, registering to `add_context()`.
/// This will run `add_context()` when the atom is hovered over by an item for context.
/// `add_context()` will *not* be called unless this is run.
/// This is not necessary for Type-B interactions, as you can just apply the flag and register to the signal yourself.
/atom/proc/register_context()
flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
RegisterSignal(src, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, .proc/add_context)
/// Creates a "Type-B" contextual screentip interaction.
/// When a user hovers over this, this proc will be called in order
/// to provide context for contextual screentips.
/// You must call `register_context()` in order for this to be registered.
/// A screentip context list is a list that has context keys (SCREENTIP_CONTEXT_*, from __DEFINES/screentips.dm)
/// that map to the action as text.
/// If you mutate the list in this signal, you must return CONTEXTUAL_SCREENTIP_SET.
/// `source` can, in all cases, be replaced with `src`, and only exists because this proc directly connects to a signal.
/atom/proc/add_context(
atom/source,
list/context,
obj/item/held_item,
mob/living/user,
)
SIGNAL_HANDLER
return NONE
@@ -0,0 +1,73 @@
/// Apply basic contextual screentips when the user hovers over this item with an empty hand.
/// A "Type B" interaction.
/// This stacks with other contextual screentip elements, though you may want to register the signal/flag manually at that point for performance.
/datum/element/contextual_screentip_bare_hands
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
id_arg_index = 3
/// If set, the text to show for LMB
var/lmb_text
/// If set, the text to show for RMB
var/rmb_text
/// If set, the text to show for LMB when in combat mode. Otherwise, defaults to lmb_text.
var/lmb_text_combat_mode
/// If set, the text to show for RMB when in combat mode. Otherwise, defaults to rmb_text.
var/rmb_text_combat_mode
// If you're curious about `use_named_parameters`, it's because you should use named parameters!
// AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Do the thing")
/datum/element/contextual_screentip_bare_hands/Attach(
datum/target,
use_named_parameters,
lmb_text,
rmb_text,
lmb_text_combat_mode,
rmb_text_combat_mode,
)
. = ..()
if (!isatom(target))
return ELEMENT_INCOMPATIBLE
if (!isnull(use_named_parameters))
CRASH("Use named parameters instead of positional ones.")
src.lmb_text = lmb_text
src.rmb_text = rmb_text
src.lmb_text_combat_mode = lmb_text_combat_mode || lmb_text
src.rmb_text_combat_mode = rmb_text_combat_mode || rmb_text
var/atom/atom_target = target
atom_target.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
RegisterSignal(atom_target, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, .proc/on_requesting_context_from_item)
/datum/element/contextual_screentip_bare_hands/Detach(datum/source, ...)
UnregisterSignal(source, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM)
// We don't remove HAS_CONTEXTUAL_SCREENTIPS_1, since there could be other stuff still hooked to it,
// and being set without signals is not dangerous, just less performant.
// A lot of things don't do this, perhaps make a proc that checks if any signals are still set, and if not,
// remove the flag.
return ..()
/datum/element/contextual_screentip_bare_hands/proc/on_requesting_context_from_item(
datum/source,
list/context,
obj/item/held_item,
mob/living/user,
)
SIGNAL_HANDLER
if (!isnull(held_item))
return NONE
if (!isnull(lmb_text))
context[SCREENTIP_CONTEXT_LMB] = user.a_intent == INTENT_HARM ? lmb_text_combat_mode : lmb_text
if (!isnull(rmb_text))
context[SCREENTIP_CONTEXT_RMB] = user.a_intent == INTENT_HARM ? rmb_text_combat_mode : rmb_text
return CONTEXTUAL_SCREENTIP_SET
@@ -0,0 +1,47 @@
/// Apply basic contextual screentips when the user hovers over this item with a provided item.
/// A "Type B" interaction.
/// This stacks with other contextual screentip elements, though you may want to register the signal/flag manually at that point for performance.
/datum/element/contextual_screentip_item_typechecks
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
id_arg_index = 2
/// Map of item paths to contexts to usages
var/list/item_paths_to_contexts
/datum/element/contextual_screentip_item_typechecks/Attach(datum/target, item_paths_to_contexts)
. = ..()
if (!isatom(target))
return ELEMENT_INCOMPATIBLE
src.item_paths_to_contexts = item_paths_to_contexts
var/atom/atom_target = target
atom_target.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
RegisterSignal(atom_target, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, .proc/on_requesting_context_from_item)
/datum/element/contextual_screentip_item_typechecks/Detach(datum/source, ...)
UnregisterSignal(source, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM)
// We don't remove HAS_CONTEXTUAL_SCREENTIPS_1, since there could be other stuff still hooked to it,
// and being set without signals is not dangerous, just less performant.
// A lot of things don't do this, perhaps make a proc that checks if any signals are still set, and if not,
// remove the flag.
return ..()
/datum/element/contextual_screentip_item_typechecks/proc/on_requesting_context_from_item(
datum/source,
list/context,
obj/item/held_item,
)
SIGNAL_HANDLER
if (isnull(held_item))
return NONE
for (var/item_path in item_paths_to_contexts)
if (istype(held_item, item_path))
context += item_paths_to_contexts[item_path]
return CONTEXTUAL_SCREENTIP_SET
return NONE
@@ -0,0 +1,48 @@
/// Apply basic contextual screentips when the user hovers over this item with an item of the given tool behavior.
/// A "Type B" interaction.
/// This stacks with other contextual screentip elements, though you may want to register the signal/flag manually at that point for performance.
/datum/element/contextual_screentip_tools
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
id_arg_index = 2
/// Map of tool behaviors to contexts to usages
var/list/tool_behaviors
/datum/element/contextual_screentip_tools/Attach(datum/target, tool_behaviors)
. = ..()
if (!isatom(target))
return ELEMENT_INCOMPATIBLE
src.tool_behaviors = tool_behaviors
var/atom/atom_target = target
atom_target.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
RegisterSignal(atom_target, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, .proc/on_requesting_context_from_item)
/datum/element/contextual_screentip_tools/Detach(datum/source, ...)
UnregisterSignal(source, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM)
// We don't remove HAS_CONTEXTUAL_SCREENTIPS_1, since there could be other stuff still hooked to it,
// and being set without signals is not dangerous, just less performant.
// A lot of things don't do this, perhaps make a proc that checks if any signals are still set, and if not,
// remove the flag.
return ..()
/datum/element/contextual_screentip_tools/proc/on_requesting_context_from_item(
datum/source,
list/context,
obj/item/held_item,
)
SIGNAL_HANDLER
if (isnull(held_item))
return NONE
var/tool_behavior = held_item.tool_behaviour
if (!(tool_behavior in tool_behaviors))
return NONE
context += tool_behaviors[tool_behavior]
return CONTEXTUAL_SCREENTIP_SET
@@ -0,0 +1,29 @@
/// Create a "Type-A" contextual screentip interaction, registering to `add_item_context()`.
/// This will run `add_item_context()` when the item hovers over another object for context.
/// `add_item_context()` will *not* be called unless this is run.
/// This is not necessary for Type-A interactions, as you can just apply the flag and register to the signal yourself.
/obj/item/proc/register_item_context()
item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS
RegisterSignal(
src,
COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET,
.proc/add_item_context,
)
/// Creates a "Type-A" contextual screentip interaction.
/// When a user hovers over something with this item in hand, this proc will be called in order
/// to provide context for contextual screentips.
/// You must call `register_item_context()` in order for this to be registered.
/// A screentip context list is a list that has context keys (SCREENTIP_CONTEXT_*, from __DEFINES/screentips.dm)
/// that map to the action as text.
/// If you mutate the list in this signal, you must return CONTEXTUAL_SCREENTIP_SET.
/// `source` can, in all cases, be replaced with `src`, and only exists because this proc directly connects to a signal.
/obj/item/proc/add_item_context(
obj/item/source,
list/context,
atom/target,
mob/living/user,
)
SIGNAL_HANDLER
return NONE