Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Screentips #11938

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft

Screentips #11938

wants to merge 25 commits into from

Conversation

PowerfulBacon
Copy link
Member

@PowerfulBacon PowerfulBacon commented Nov 24, 2024

About The Pull Request

Implements the foundation for contextual screentips.

Contains a partial port of: tgstation/tgstation#76889 since I struggled to find documentation on images in maptext.
Contains a partial port of tgstation/tgstation#63567 as I accessed the code to see how TG solved the MouseEntered overhead problem, though I have optimised the code slightly.
The TG code was mainly used to ensure that our system has some resemblance to the TG code, and only makes changes where necessary.
The rest of the code is original code.

It is important to note that simply defining MouseEntered on /atom will incur a pretty significant amount of overhead due to byond enabling networking of mouse entered actions when this is defined, but this is a cost that we are going to have to accept. I did try to implement this clientside, though I faced problems with the label component and it would be impossible to include contextual data. If we are going to have this cost, then we might as well make it as good as it gets.

There are some limitations that I encountered while creating this:

  • Icons in maptext don't get properly aligned unless there is text preceeding them on the line.

Limitations

The context objects will have a lot of proc-calls on objects that have a lot of actions. This may need a testmerge for testing, though since our pops aren't huge and there won't be many things with too many, I don't think the issue will be too great (it's also tied to a subsystem anyway, with low priority).

In the future we may be able to introduce some kind of compilation step that inlines procs for files that have been editted in production.

Adding screentips to objects

Adding screentips to objects is a matter of defining one of the context procs:

  • add_context_self: Defines contextual actions for an object

Context procs pass through a screentip context object, which includes procs that help you define the tips that appear on the object. The context handles styling itself so that we don't end up with inconsistent tips

Context procs:

Method Description
accept_mob_type(mob_type) Can be used in if statements to check if a mob matches a certain type, if it does then the screentip will show for that mob. By default screentips only show for humans
accept_silicons Can be used in if statements to check if the user is a silicon, if it does then the screentip will show for that mob. By default screentips only show for humans
accept_animals Can be used in if statements to check if the user is an animal, if it does then the screentip will show for that mob. By default screentips only show for humans
add_context(message) Adds some generic context message to the object. Should be avoided where possible.
add_access_context(message, has_access) Adds an access message to the object, which will show red if the user doesn't have access and green if the user has access.
add_left_click_action Adds an action that can be performed via left click to the object
add_left_click_item_action Adds an action that can be performed via left click to the object, as long as the user is holding the item
use_cache If true, then the item will be cached per the mob type. This should be set if the object's action list doesn't change based on an external state.

Example

/obj/machinery/door/airlock/add_context_self(datum/screentip_context/context, mob/user, obj/item/item)
	..()
	if (machine_stat & BROKEN)	// External state
		return
	if (!hasPower())	// External state
		context.add_left_click_item_action("Pry Open", /obj/item/crowbar)
		return
	// Handle lazy initalisation of access
	if (length(req_access) || length(req_one_access) || req_access_txt != "0" || req_one_access_txt != "0")	// Constant, so not external state
		context.add_access_context("Access Required", allowed(user))
	context.add_left_click_item_action("[panel_open ? "Close" : "Open"] Maintenance Panel", /obj/item/screwdriver)
	if (panel_open)	// External state
		context.add_left_click_item_action("Hack Maintenance Panel", /obj/item/multitool)
		context.add_left_click_item_action("Hack Maintenance Panel", /obj/item/wirecutters)
	if (context.accept_silicons())
		context.add_shift_click_action("Open", (lights && locked) ? "Blocked" : null, (!lights || !locked) && canAIControl(user))
		context.add_ctrl_click_action("[locked ? "Raise" : "Drop"] Bolts", "Blocked", canAIControl(user))
		context.add_alt_click_action("[secondsElectrified ? "Un-electrify" : "Electrify"]", "Blocked", canAIControl(user))
		context.add_ctrl_shift_click_action("[emergency ? "Disbale" : "Enable"] Emergency Access", "Blocked", canAIControl(user))
	else
		context.add_left_click_action("Open", (lights && locked) ? "Bolted" : null, (!lights || !locked))

Caching (WIP)

Screentips that don't depend on external state can call context.use_cache which, for every input state, will cache the results and re-use it for the rest of the round when that type is encountered in the screentip system again. These must only depend on pre-selected conditions.

Input states are as follows:

  • attack_hand: Message shown only when the mob is a human and has nothing in their hand.

If caching is enabled, then the cache will be built as required, requiring no additional effort from the developer:

context.use_cache()
if (context.accept_silicon())
	if (context.accept_mob_type(/mob/living/silicon/ai))
		context.add_left_mouse_action("Click as AI")
		return
	context.add_left_mouse_action("Click as robot")
else
	context.add_left_mouse_action("Click as human")

The above code will work the following way:

  • When the screentip is first called as a human, we will cache the human state as 'Click as human' and a condition as added that the cache accepts silicons (since we saw that silicons were accepted)
  • When the screentip is first called as a silicon, the context becomes aware that the AI mob type is also accepted, which if we ever encounter it will generate another part to the cache. We add the silicon's message to the silicon cache.
  • When an AI is seen, the context is generated for the AI mob and the result is cached.
  • When any mob that isn't accepted by the cache at any point is seen, then the cache will let the context know that there are no screentips for this object.

Any external state will break assumptions that the cache makes and will cause incorrect messages to show on the screentip.

While running in debug mode, an extra screentip line is added that tells the developer the cache is active for that object.

image

Autotips

To make adding tips easier, I have created a system for auto-tips. These define paths which get automatically added to the global screentip cache upon initialisation.

Since they use the cache, auto-tips cannot depend on external state.

They are defined as follows:

SCREENTIP_ATTACK_HAND(/obj/machinery/button, "Press")

See caching to see the input states.

Why It's Good For The Game

Testing Photographs and Procedure

image

image

image

image

Changelog

🆑
add: Adds contextual screentips
/:cl:

@Geatish
Copy link
Contributor

Geatish commented Nov 24, 2024

Good pr, but there will be an option to turn this off right? I hate having a cluttered screen.
Also something to think about, catching the name of people without examining them isn't ideal, makes it way harder to get away with your identity.

@PowerfulBacon
Copy link
Member Author

It will be a preference, if you can mouse over somebody, you will be able to see their name (which you can do already with the status bar).

Copy link
Member

@EvilDragonfiend EvilDragonfiend left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the term add to various proc names sounds very off. It sounds like you're adding an object to a list, and it sounds there's a function 'remove' to be paired with it.
I'd consider using different term. For example:

				// instead of 'add', used 'build'
/datum/ai_controller/dog/proc/build_context(datum/source, datum/screentip_context/context, mob/user)
	if (user.a_intent == INTENT_HARM)
		context.assign_action_attack_hand("Punch")
	else
		context.assign_action_attack_hand("Pet")
		// Changed from 'add_attack_hand_action("Thing")'

Technically, 'add' describes how exactly the code works, but the current proc name sounds that you can do that multiple times. I mean

/proc/foo(context)
	context.add_attack_hand_action("take item")
	context.add_attack_hand_action("do thing")
	context.add_attack_hand_action("punch thing")

This isn't a thing, but the name makes it sounds possible.

code/modules/screentips/atom_extensions.dm Outdated Show resolved Hide resolved
@EvilDragonfiend
Copy link
Member

ah, also, I don't like the term context is used here. I know it is screentip_context, but it may cause confusion with context menu. Not sure if there's a better way to replace it, but please be noted.

@PowerfulBacon
Copy link
Member Author

PowerfulBacon commented Nov 25, 2024

Context is programming standard.

/proc/foo(context)
	context.add_attack_hand_action("take item")
	context.add_attack_hand_action("do thing")
	context.add_attack_hand_action("punch thing")

Also this is possible, it will display all 3 actions

@EvilDragonfiend
Copy link
Member

EvilDragonfiend commented Nov 25, 2024

Context is programming standard.

I just worried about a case. I hope future coders won't be confused.

/proc/foo(context)
	context.add_attack_hand_action("take item")
	context.add_attack_hand_action("do thing")
	context.add_attack_hand_action("punch thing")

Also this is possible, it will display all 3 actions

Yeah, I know it's possible, but such practice doesn't make sense to me.
Why would you do that for what? Why don't you do context.add_attack_hand_action("take item / do thing / punch thing") instead? or putting <br> or something?

@PowerfulBacon
Copy link
Member Author

Each individual call also adds a new message, you aren't just setting a single context message but you can add multiple messages to the context for different controls (add a left click, then add a right click, then add a control click, etc.)

@llol111
Copy link
Contributor

llol111 commented Nov 25, 2024

You know, i have conflicted feelings about this.
Since i can turn it off in prefs, theres realy no reason to complain i suppose. And its bound to help newfriends alot alot, which is... good, i guess?
Yet i cant help feel like its bad to add even more screen clutter and "crutches" 🤔

@EvilDragonfiend
Copy link
Member

EvilDragonfiend commented Nov 26, 2024

Each individual call also adds a new message, you aren't just setting a single context message but you can add multiple messages to the context for different controls (add a left click, then add a right click, then add a control click, etc.)

That doesn't look what I mean. You don't set multiple contexts to a single action. (in codewise, I know it's possible, but again, for what and why?)
The example I wrote does 'add' thing to a single action multiple times. What would showing 3 contexts from a left-click mean? that it would do 3 things at the same time from a single click?
So that I thought it'd be better to be named as 'assign' or 'set' smh

If you think it suits there, would you please differentiate a proc 'add' that calls procs doing 'add' at least?
like build_screentip_context -> add_action or add_context -> append_action or anything

@Tsar-Salat
Copy link
Contributor

Tsar-Salat commented Nov 26, 2024

Yet i cant help feel like its bad to add even more screen clutter and "crutches" 🤔

You have a vehement dislike of combat mode that you wrote in cope Haiku or whatever on my pr.

This PR will certainly help you and others when learning the new controls. I would not discount that its only for newfriends, I think it will be of assistance to a lot of people.

I personally look at the wiki way too much for certain jobs, so I added tool act interactions to Bacon's branch so you can tell what is made in kitchen with what tools just by hovering over it.

@PowerfulBacon
Copy link
Member Author

Anything good for new players is generally good for experienced ones too and makes their gameplay experience better

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants