-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathseclite_attachable.dm
298 lines (241 loc) · 10.7 KB
/
seclite_attachable.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
/**
* Component which allows you to attach a seclight to an item,
* be it a piece of clothing or a tool.
*/
/datum/component/seclite_attachable
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// Whether we can remove the light with a screwdriver or not.
var/is_light_removable = TRUE
/// If passed, we wil simply update our item's icon_state when a light is attached.
/// Formatted as parent_base_state-[light_icon-state]-"on"
var/light_icon_state
/// If passed, we will add overlays to the item when a light is attached.
/// This is the icon file it grabs the overlay from.
var/light_overlay_icon
/// The state to take from the light overlay icon if supplied.
var/light_overlay
/// The X offset of our overlay if supplied.
var/overlay_x = 0
/// The Y offset of our overlay if supplied.
var/overlay_y = 0
// Internal vars.
/// A reference to the actual light that's attached.
var/obj/item/flashlight/seclite/light
/// A weakref to the item action we add with the light.
var/datum/weakref/toggle_action_ref
/// Static typecache of all lights we consider seclites (all lights we can attach).
var/static/list/valid_lights = typecacheof(list(/obj/item/flashlight/seclite))
/datum/component/seclite_attachable/Initialize(
obj/item/flashlight/seclite/starting_light,
is_light_removable = TRUE,
light_icon_state,
light_overlay_icon,
light_overlay,
overlay_x = 0,
overlay_y = 0,
)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
src.is_light_removable = is_light_removable
src.light_icon_state = light_icon_state
src.light_overlay_icon = light_overlay_icon
src.light_overlay = light_overlay
src.overlay_x = overlay_x
src.overlay_y = overlay_y
if(istype(starting_light))
add_light(starting_light)
/datum/component/seclite_attachable/Destroy(force, silent)
if(light)
remove_light()
return ..()
// Inheriting component allows lights to be added externally to things which already have a mount.
/datum/component/seclite_attachable/InheritComponent(
datum/component/seclite_attachable/new_component,
original,
obj/item/flashlight/seclite/starting_light,
is_light_removable = TRUE,
light_icon_state,
light_overlay_icon,
light_overlay,
overlay_x,
overlay_y,
)
if(!original)
return
src.is_light_removable = is_light_removable
// For the rest of these arguments, default to what already exists
if(light_icon_state)
src.light_icon_state = light_icon_state
if(light_overlay_icon)
src.light_overlay_icon = light_overlay_icon
if(light_overlay)
src.light_overlay = light_overlay
if(overlay_x)
src.overlay_x = overlay_x
if(overlay_x)
src.overlay_y = overlay_y
if(istype(starting_light))
add_light(starting_light)
/datum/component/seclite_attachable/RegisterWithParent()
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(on_parent_deconstructed))
RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(on_light_exit))
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
RegisterSignal(parent, COMSIG_ITEM_UI_ACTION_CLICK, PROC_REF(on_action_click))
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_deleted))
/datum/component/seclite_attachable/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_OBJ_DECONSTRUCT,
COMSIG_ATOM_EXITED,
COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
COMSIG_ATOM_UPDATE_ICON_STATE,
COMSIG_ATOM_UPDATE_OVERLAYS,
COMSIG_ITEM_UI_ACTION_CLICK,
COMSIG_PARENT_ATTACKBY,
COMSIG_PARENT_EXAMINE,
COMSIG_PARENT_QDELETING,
))
/// Sets a new light as our current light for our parent.
/datum/component/seclite_attachable/proc/add_light(obj/item/flashlight/new_light, mob/attacher)
if(light)
CRASH("[type] tried to add a new light when it already had one.")
light = new_light
light.set_light_flags(light.light_flags | LIGHT_ATTACHED)
// We may already exist within in our parent's contents... But if we don't move it over now
if(light.loc != parent)
light.forceMove(parent)
// We already have an action for the light for some reason? Clean it up
if(toggle_action_ref?.resolve())
stack_trace("[type] - add_light had an existing toggle action when add_light was called.")
QDEL_NULL(toggle_action_ref)
// Make a new toggle light item action for our parent
var/obj/item/item_parent = parent
var/datum/action/item_action/toggle_seclight/toggle_action = item_parent.add_item_action(/datum/action/item_action/toggle_seclight)
toggle_action_ref = WEAKREF(toggle_action)
update_light()
/// Removes the current light from our parent.
/datum/component/seclite_attachable/proc/remove_light()
// Our action may be linked to our parent,
// but it's really sourced from our light. Get rid of it.
QDEL_NULL(toggle_action_ref)
// It is possible the light was removed by being deleted.
if(!QDELETED(light))
light.set_light_flags(light.light_flags & ~LIGHT_ATTACHED)
light.update_brightness()
light = null
update_light()
/// Toggles the light within on or off.
/// Returns TRUE if there is a light inside, FALSE otherwise.
/datum/component/seclite_attachable/proc/toggle_light(mob/user)
if(!light)
return FALSE
light.on = !light.on
light.update_brightness()
if(user)
to_chat(user, span_notice("You [light.on ? "turn on":"turn off"] [light]."))
playsound(light, 'sound/weapons/empty.ogg', 100, TRUE)
update_light()
return TRUE
/// Called after the a light is added, removed, or toggles.
/// Ensures all of our appearances look correct for the new light state.
/datum/component/seclite_attachable/proc/update_light()
var/obj/item/item_parent = parent
item_parent.update_appearance()
item_parent.update_action_buttons()
/// Signal proc for [COMSIG_ATOM_EXITED] that handles our light being removed or deleted from our parent.
/datum/component/seclite_attachable/proc/on_light_exit(obj/item/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(gone == light)
remove_light()
/// Signal proc for [COMSIG_OBJ_DECONSTRUCT] that drops our light to the ground if our parent is deconstructed.
/datum/component/seclite_attachable/proc/on_parent_deconstructed(obj/item/source, disassembled)
SIGNAL_HANDLER
// Our light is gone already - Probably destroyed by whatever destroyed our parent. Just remove it.
if(QDELETED(light) || !is_light_removable)
remove_light()
return
// We were deconstructed in any other way, so we can just drop the light on the ground (which removes it via signal).
light.forceMove(source.drop_location())
/// Signal proc for [COMSIG_PARENT_QDELETING] that deletes our light if our parent is deleted.
/datum/component/seclite_attachable/proc/on_parent_deleted(obj/item/source)
SIGNAL_HANDLER
QDEL_NULL(light)
/// Signal proc for [COMSIG_ITEM_UI_ACTION_CLICK] that toggles our light on and off if our action button is clicked.
/datum/component/seclite_attachable/proc/on_action_click(obj/item/source, mob/user, datum/action)
SIGNAL_HANDLER
// This isn't OUR action specifically, we don't care.
if(!IS_WEAKREF_OF(action, toggle_action_ref))
return
// Toggle light fails = no light attached = shouldn't be possible
if(!toggle_light(user))
CRASH("[type] - on_action_click somehow both HAD AN ACTION and also HAD A TRIGGERABLE ACTION, without having an attached light.")
return COMPONENT_ACTION_HANDLED
/// Signal proc for [COMSIG_PARENT_ATTACKBY] that allows a user to attach a seclite by hitting our parent with it.
/datum/component/seclite_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, params)
SIGNAL_HANDLER
if(!is_type_in_typecache(attacking_item, valid_lights))
return
if(light)
to_chat(attacker, span_warning("There is already a light attached to [source]."))
return
if(!attacker.transferItemToLoc(attacking_item, source))
return
add_light(attacking_item, attacker)
to_chat(attacker, span_notice("You attach [attacking_item] to [source]."))
return COMPONENT_NO_AFTERATTACK
/// Signal proc for [COMSIG_ATOM_TOOL_ACT] via [TOOL_SCREWDRIVER] that removes any attached seclite.
/datum/component/seclite_attachable/proc/on_screwdriver(obj/item/source, mob/user, obj/item/tool)
SIGNAL_HANDLER
if(!light || !is_light_removable)
return
INVOKE_ASYNC(src, PROC_REF(unscrew_light), source, user, tool)
return COMPONENT_BLOCK_TOOL_ATTACK
/// Invoked asyncronously from [proc/on_screwdriver]. Handles removing the light from our parent.
/datum/component/seclite_attachable/proc/unscrew_light(obj/item/source, mob/user, obj/item/tool)
tool?.play_tool_sound(source)
to_chat(user, span_notice("You unscrew [light] from [source]."))
var/obj/item/flashlight/seclite/to_remove = light
// The forcemove here will call exited on the light, and automatically update our references / etc
to_remove.forceMove(source.drop_location())
if(source.Adjacent(user) && !issilicon(user))
user.put_in_hands(to_remove)
/// Signal proc for [COMSIG_PARENT_EXAMINE] that shows our item can have / does have a seclite attached.
/datum/component/seclite_attachable/proc/on_examine(obj/item/source, mob/examiner, list/examine_list)
SIGNAL_HANDLER
if(light)
examine_list += "It has \a [light] [is_light_removable ? "mounted on it with a few <b>screws</b>" : "permanently mounted on it"]."
else
examine_list += "It has a mounting point for a <b>seclite</b>."
/// Signal proc for [COMSIG_ATOM_UPDATE_OVERLAYS] that updates our parent with our seclite overlays, if we have some.
/datum/component/seclite_attachable/proc/on_update_overlays(obj/item/source, list/overlays)
SIGNAL_HANDLER
// No overlays to add, no reason to run
if(!light_overlay || !light_overlay_icon)
return
// No light, nothing to add
if(!light)
return
var/overlay_state = "[light_overlay][light.on ? "_on":""]"
var/mutable_appearance/flashlight_overlay = mutable_appearance(light_overlay_icon, overlay_state)
flashlight_overlay.pixel_x = overlay_x
flashlight_overlay.pixel_y = overlay_y
overlays += flashlight_overlay
/// Signal proc for [COMSIG_ATOM_UPDATE_ICON_STATE] that updates our parent's icon state, if we have one.
/datum/component/seclite_attachable/proc/on_update_icon_state(obj/item/source)
SIGNAL_HANDLER
// No icon state to set, no reason to run
if(!light_icon_state)
return
// Get the "base icon state" to work on
var/base_state = source.base_icon_state || initial(source.icon_state)
// Updates our icon state based on our light state.
if(light)
source.icon_state = "[base_state]-[light_icon_state][light.on ? "-on":""]"
// Reset their icon state when if we've got no light.
else if(source.icon_state != base_state)
// Yes, this might mess with other icon state alterations,
// but that's the downside of using icon states over overlays.
source.icon_state = base_state