-
-
Notifications
You must be signed in to change notification settings - Fork 444
/
Copy pathaction.dm
400 lines (341 loc) · 14.6 KB
/
action.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/**
* # Action system
*
* A simple base for an modular behavior attached to atom or datum.
*/
/datum/action
/// The name of the action
var/name = "Generic Action"
/// The description of what the action does
var/desc
/// The target the action is attached to. If the target datum is deleted, the action is as well.
/// Set in New() via the proc link_to(). PLEASE set a target if you're making an action
var/datum/target
/// Where any buttons we create should be by default. Accepts screen_loc and location defines
var/default_button_position = SCRN_OBJ_IN_LIST
/// This is who currently owns the action, and most often, this is who is using the action if it is triggered
/// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove()
var/mob/owner
/// If False, the owner of this action does not get a hud and cannot activate it on their own
var/owner_has_control = TRUE
/// Flags that will determine of the owner / user of the action can... use the action
var/check_flags = NONE
/// Whether the button becomes transparent when it can't be used or just reddened
var/transparent_when_unavailable = TRUE
///List of all mobs that are viewing our action button -> A unique movable for them to view.
var/list/viewers = list()
/// If TRUE, this action button will be shown to observers / other mobs who view from this action's owner's eyes.
/// Used in [/mob/proc/show_other_mob_action_buttons]
var/show_to_observers = TRUE
/// The style the button's tooltips appear to be
var/buttontooltipstyle = ""
/// This is the file for the BACKGROUND icon of the button
var/background_icon = 'icons/mob/actions/backgrounds.dmi'
/// This is the icon state state for the BACKGROUND icon of the button
var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
/// This is the file for the icon that appears OVER the button background
var/button_icon = 'icons/mob/actions.dmi'
/// This is the icon state for the icon that appears OVER the button background
var/button_icon_state = "default"
/// This is the file for any FOREGROUND overlay icons on the button (such as borders)
var/overlay_icon = 'icons/mob/actions/backgrounds.dmi'
/// This is the icon state for any FOREGROUND overlay icons on the button (such as borders)
var/overlay_icon_state
var/atom/movable/screen/movable/action_button/button = null
/datum/action/New(Target)
link_to(Target)
/// Links the passed target to our action, registering any relevant signals
/datum/action/proc/link_to(Target)
target = Target
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE)
if(isatom(target))
RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(on_target_icon_update))
if(istype(target, /datum/mind))
RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_target_mind_swapped))
/datum/action/Destroy()
if(owner)
Remove(owner)
target = null
QDEL_LIST_ASSOC_VAL(viewers) // Qdel the buttons in the viewers list **NOT THE HUDS**
return ..()
/// Signal proc that clears any references based on the owner or target deleting
/// If the owner's deleted, we will simply remove from them, but if the target's deleted, we will self-delete
/datum/action/proc/clear_ref(datum/ref)
SIGNAL_HANDLER
if(ref == owner)
Remove(owner)
if(ref == target)
qdel(src)
/// Grants the action to the passed mob, making it the owner
/datum/action/proc/Grant(mob/grant_to, ...)
if(isnull(grant_to))
Remove(owner)
return
if(grant_to == owner)
return // We already have it
var/mob/previous_owner = owner
owner = grant_to
if(!isnull(previous_owner))
Remove(previous_owner)
SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, owner)
SEND_SIGNAL(owner, COMSIG_MOB_GRANTED_ACTION, src)
RegisterSignal(owner, COMSIG_QDELETING, PROC_REF(clear_ref), override = TRUE)
// Register some signals based on our check_flags
// so that our button icon updates when relevant
if(check_flags & AB_CHECK_CONSCIOUS)
RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(update_status_on_signal))
if(check_flags & AB_CHECK_INCAPACITATED)
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED), PROC_REF(update_status_on_signal))
if(check_flags & AB_CHECK_IMMOBILE)
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), PROC_REF(update_status_on_signal))
if(check_flags & AB_CHECK_HANDS_BLOCKED)
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(update_status_on_signal))
if(check_flags & AB_CHECK_LYING)
RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(update_status_on_signal))
if(owner_has_control)
GiveAction(grant_to)
/// Remove the passed mob from being owner of our action
/datum/action/proc/Remove(mob/remove_from)
SHOULD_CALL_PARENT(TRUE)
for(var/datum/hud/hud in viewers)
if(!hud.mymob)
continue
HideFrom(hud.mymob)
LAZYREMOVE(remove_from.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
viewers = list()
if(isnull(owner))
return
SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner)
SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src)
UnregisterSignal(owner, COMSIG_QDELETING)
// Clean up our check_flag signals
UnregisterSignal(owner, list(
COMSIG_LIVING_SET_BODY_POSITION,
COMSIG_MOB_STATCHANGE,
SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED),
SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED),
SIGNAL_ADDTRAIT(TRAIT_INCAPACITATED),
))
if(target == owner)
RegisterSignal(target, COMSIG_QDELETING, PROC_REF(clear_ref))
if(owner == remove_from)
owner = null
/// Actually triggers the effects of the action.
/// Called when the on-screen button is clicked, for example.
/datum/action/proc/Trigger(trigger_flags)
if(!IsAvailable(feedback = TRUE))
return FALSE
if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
return FALSE
return TRUE
/**
* Whether our action is currently available to use or not
* * feedback - If true this is being called to check if we have any messages to show to the owner
*/
/datum/action/proc/IsAvailable(feedback = FALSE)
if(!owner)
return FALSE
if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED))
if (feedback)
owner.balloon_alert(owner, "hands blocked!")
return FALSE
if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED))
if (feedback)
owner.balloon_alert(owner, "can't move!")
return FALSE
if((check_flags & AB_CHECK_INCAPACITATED) && HAS_TRAIT(owner, TRAIT_INCAPACITATED))
if (feedback)
owner.balloon_alert(owner, "incapacitated!")
return FALSE
if((check_flags & AB_CHECK_LYING) && isliving(owner))
var/mob/living/action_user = owner
if(!(action_user.mobility_flags & MOBILITY_STAND))
if (feedback)
owner.balloon_alert(owner, "must stand up!")
return FALSE
if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS)
if (feedback)
owner.balloon_alert(owner, "unconscious!")
return FALSE
return TRUE
/// Builds / updates all buttons we have shared or given out
/datum/action/proc/build_all_button_icons(update_flags = ALL, force)
for(var/datum/hud/hud as anything in viewers)
build_button_icon(viewers[hud], update_flags, force)
/**
* Builds the icon of the button.
*
* Concept:
* - Underlay (Background icon)
* - Icon (button icon)
* - Maptext
* - Overlay (Background border)
*
* button - which button we are modifying the icon of
* force - whether we're forcing a full update
*/
/datum/action/proc/build_button_icon(atom/movable/screen/movable/action_button/button, update_flags = ALL, force = FALSE)
if(!button)
return
if(update_flags & UPDATE_BUTTON_NAME)
update_button_name(button, force)
if(update_flags & UPDATE_BUTTON_BACKGROUND)
apply_button_background(button, force)
if(update_flags & UPDATE_BUTTON_ICON)
apply_button_icon(button, force)
if(update_flags & UPDATE_BUTTON_OVERLAY)
apply_button_overlay(button, force)
if(update_flags & UPDATE_BUTTON_STATUS)
update_button_status(button, force)
/**
* Updates the name and description of the button to match our action name and discription.
*
* current_button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/update_button_name(atom/movable/screen/movable/action_button/button, force = FALSE)
button.name = name
if(desc)
button.desc = desc
/**
* Creates the background underlay for the button
*
* current_button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/apply_button_background(atom/movable/screen/movable/action_button/current_button, force = FALSE)
if(!background_icon || !background_icon_state || (current_button.active_underlay_icon_state == background_icon_state && !force))
return
// What icons we use for our background
var/list/icon_settings = list(
// The icon file
"bg_icon" = background_icon,
// The icon state, if is_action_active() returns FALSE
"bg_state" = background_icon_state,
// The icon state, if is_action_active() returns TRUE
"bg_state_active" = background_icon_state,
)
// If background_icon_state is ACTION_BUTTON_DEFAULT_BACKGROUND instead use our hud's action button scheme
if(background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND && owner?.hud_used)
icon_settings = owner.hud_used.get_action_buttons_icons()
// Determine which icon to use
var/used_icon_key = is_action_active(current_button) ? "bg_state_active" : "bg_state"
// Make the underlay
current_button.underlays.Cut()
current_button.underlays += image(icon = icon_settings["bg_icon"], icon_state = icon_settings[used_icon_key])
current_button.active_underlay_icon_state = icon_settings[used_icon_key]
/**
* Applies our button icon and icon state to the button
*
* current_button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/apply_button_icon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
if(!button_icon || !button_icon_state || (current_button.icon_state == button_icon_state && !force))
return
current_button.icon = button_icon
current_button.icon_state = button_icon_state
current_button.actiontooltipstyle = buttontooltipstyle
/**
* Applies any overlays to our button
*
* current_button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force = FALSE)
SEND_SIGNAL(src, COMSIG_ACTION_OVERLAY_APPLY, current_button, force)
if(!overlay_icon || !overlay_icon_state || (current_button.active_overlay_icon_state == overlay_icon_state && !force))
return
current_button.cut_overlay(current_button.button_overlay)
current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = overlay_icon_state)
current_button.add_overlay(current_button.button_overlay)
current_button.active_overlay_icon_state = overlay_icon_state
/**
* Any other miscellaneous "status" updates within the action button is handled here,
* such as redding out when unavailable or modifying maptext.
*
* current_button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/update_button_status(atom/movable/screen/movable/action_button/current_button, force = FALSE)
if(IsAvailable(feedback = FALSE))
current_button.color = rgb(255,255,255,255)
else
current_button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
/// Gives our action to the passed viewer.
/// Puts our action in their actions list and shows them the button.
/datum/action/proc/GiveAction(mob/viewer)
var/datum/hud/our_hud = viewer.hud_used
if(viewers[our_hud]) // Already have a copy of us? go away
return
LAZYOR(viewer.actions, src) // Move this in
ShowTo(viewer)
/// Adds our action button to the screen of the passed viewer.
/datum/action/proc/ShowTo(mob/viewer)
var/datum/hud/our_hud = viewer.hud_used
if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place
return
var/atom/movable/screen/movable/action_button/button = create_button()
SetId(button, viewer)
button.our_hud = our_hud
viewers[our_hud] = button
if(viewer.client)
viewer.client.screen += button
button.load_position(viewer)
viewer.update_action_buttons()
/// Removes our action from the passed viewer.
/datum/action/proc/HideFrom(mob/viewer)
var/datum/hud/our_hud = viewer.hud_used
var/atom/movable/screen/movable/action_button/button = viewers[our_hud]
LAZYREMOVE(viewer.actions, src)
if(button)
qdel(button)
/// Creates an action button movable for the passed mob, and returns it.
/datum/action/proc/create_button()
var/atom/movable/screen/movable/action_button/button = new()
button.linked_action = src
build_button_icon(button, ALL, TRUE)
return button
/datum/action/proc/SetId(atom/movable/screen/movable/action_button/our_button, mob/owner)
//button id generation
var/bitfield = 0
for(var/datum/action/action in owner.actions)
if(action == src) // This could be us, which is dumb
continue
var/atom/movable/screen/movable/action_button/button = action.viewers[owner.hud_used]
if(action.name == name && button.id)
bitfield |= button.id
bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one
for(var/i in 0 to 23) // We get 24 possible bitflags in dm
var/bitflag = 1 << i // Shift us over one
if(bitfield & bitflag)
our_button.id = bitflag
return
/// Updates our buttons if our target's icon was updated
/datum/action/proc/on_target_icon_update(datum/source, updates, updated)
SIGNAL_HANDLER
var/update_flag = ALL
var/forced = TRUE
if(updates & UPDATE_ICON_STATE)
update_flag |= UPDATE_BUTTON_ICON
forced = TRUE
if(updates & UPDATE_OVERLAYS)
update_flag |= UPDATE_BUTTON_OVERLAY
forced = TRUE
if(updates & (UPDATE_NAME|UPDATE_DESC))
update_flag |= UPDATE_BUTTON_NAME
// Status is not relevant, and background is not relevant. Neither will change
// Force the update if an icon state or overlay change was done
build_all_button_icons(update_flag, forced)
/// A general use signal proc that reacts to an event and updates JUST our button's status
/datum/action/proc/update_status_on_signal(datum/source)
SIGNAL_HANDLER
build_all_button_icons(UPDATE_BUTTON_STATUS)
/// Signal proc for COMSIG_MIND_TRANSFERRED - for minds, transfers our action to our new mob on mind transfer
/datum/action/proc/on_target_mind_swapped(datum/mind/source, mob/old_current)
SIGNAL_HANDLER
// Grant() calls Remove() from the existing owner so we're covered on that
Grant(source.current)
/// Checks if our action is actively selected. Used for selecting icons primarily.
/datum/action/proc/is_action_active(atom/movable/screen/movable/action_button/current_button)
return FALSE