/
augments_arms.dm
481 lines (410 loc) · 18.4 KB
/
augments_arms.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
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
/obj/item/organ/internal/cyberimp/arm
name = "arm-mounted implant"
desc = "You shouldn't see this! Adminhelp and report this as an issue on github!"
zone = BODY_ZONE_R_ARM
icon_state = "implant-toolkit"
w_class = WEIGHT_CLASS_SMALL
actions_types = list(/datum/action/item_action/organ_action/toggle)
///A ref for the arm we're taking up. Mostly for the unregister signal upon removal
var/obj/hand
//A list of typepaths to create and insert into ourself on init
var/list/items_to_create = list()
/// Used to store a list of all items inside, for multi-item implants.
var/list/items_list = list()// I would use contents, but they shuffle on every activation/deactivation leading to interface inconsistencies.
/// You can use this var for item path, it would be converted into an item on New().
var/obj/item/active_item
/// Sound played when extending
var/extend_sound = 'sound/mecha/mechmove03.ogg'
/// Sound played when retracting
var/retract_sound = 'sound/mecha/mechmove03.ogg'
/obj/item/organ/internal/cyberimp/arm/Initialize(mapload)
. = ..()
if(ispath(active_item))
active_item = new active_item(src)
items_list += WEAKREF(active_item)
for(var/typepath in items_to_create)
var/atom/new_item = new typepath(src)
items_list += WEAKREF(new_item)
update_appearance()
SetSlotFromZone()
/obj/item/organ/internal/cyberimp/arm/Destroy()
hand = null
active_item = null
for(var/datum/weakref/ref in items_list)
var/obj/item/to_del = ref.resolve()
if(!to_del)
continue
qdel(to_del)
items_list.Cut()
return ..()
/datum/action/item_action/organ_action/toggle/toolkit
desc = "You can also activate your empty hand or the tool in your hand to open the tools radial menu."
/obj/item/organ/internal/cyberimp/arm/proc/SetSlotFromZone()
switch(zone)
if(BODY_ZONE_L_ARM)
slot = ORGAN_SLOT_LEFT_ARM_AUG
if(BODY_ZONE_R_ARM)
slot = ORGAN_SLOT_RIGHT_ARM_AUG
else
CRASH("Invalid zone for [type]")
/obj/item/organ/internal/cyberimp/arm/update_icon()
. = ..()
transform = (zone == BODY_ZONE_R_ARM) ? null : matrix(-1, 0, 0, 0, 1, 0)
/obj/item/organ/internal/cyberimp/arm/examine(mob/user)
. = ..()
if(IS_ROBOTIC_ORGAN(src))
. += span_info("[src] is assembled in the [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm configuration. You can use a screwdriver to reassemble it.")
/obj/item/organ/internal/cyberimp/arm/screwdriver_act(mob/living/user, obj/item/screwtool)
. = ..()
if(.)
return TRUE
screwtool.play_tool_sound(src)
if(zone == BODY_ZONE_R_ARM)
zone = BODY_ZONE_L_ARM
else
zone = BODY_ZONE_R_ARM
SetSlotFromZone()
to_chat(user, span_notice("You modify [src] to be installed on the [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."))
update_appearance()
/obj/item/organ/internal/cyberimp/arm/on_mob_insert(mob/living/carbon/arm_owner)
. = ..()
RegisterSignal(arm_owner, COMSIG_CARBON_POST_ATTACH_LIMB, PROC_REF(on_limb_attached))
RegisterSignal(arm_owner, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(dropkey)) //We're nodrop, but we'll watch for the drop hotkey anyway and then stow if possible.
on_limb_attached(arm_owner, arm_owner.hand_bodyparts[zone == BODY_ZONE_R_ARM ? RIGHT_HANDS : LEFT_HANDS])
/obj/item/organ/internal/cyberimp/arm/on_mob_remove(mob/living/carbon/arm_owner)
. = ..()
Retract()
UnregisterSignal(arm_owner, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_KB_MOB_DROPITEM_DOWN))
on_limb_detached(hand)
/obj/item/organ/internal/cyberimp/arm/proc/on_limb_attached(mob/living/carbon/source, obj/item/bodypart/limb)
SIGNAL_HANDLER
if(!limb || QDELETED(limb) || limb.body_zone != zone)
return
if(hand)
on_limb_detached(hand)
RegisterSignal(limb, COMSIG_ITEM_ATTACK_SELF, PROC_REF(on_item_attack_self))
RegisterSignal(limb, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_detached))
hand = limb
/obj/item/organ/internal/cyberimp/arm/proc/on_limb_detached(obj/item/bodypart/source)
SIGNAL_HANDLER
if(source != hand || QDELETED(hand))
return
UnregisterSignal(hand, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_BODYPART_REMOVED))
hand = null
/obj/item/organ/internal/cyberimp/arm/proc/on_item_attack_self()
SIGNAL_HANDLER
INVOKE_ASYNC(src, PROC_REF(ui_action_click))
/obj/item/organ/internal/cyberimp/arm/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF || !IS_ROBOTIC_ORGAN(src))
return
if(prob(15/severity) && owner)
to_chat(owner, span_warning("The electromagnetic pulse causes [src] to malfunction!"))
// give the owner an idea about why his implant is glitching
Retract()
/**
* Called when the mob uses the "drop item" hotkey
*
* Items inside toolset implants have TRAIT_NODROP, but we can still use the drop item hotkey as a
* quick way to store implant items. In this case, we check to make sure the user has the correct arm
* selected, and that the item is actually owned by us, and then we'll hand off the rest to Retract()
**/
/obj/item/organ/internal/cyberimp/arm/proc/dropkey(mob/living/carbon/host)
SIGNAL_HANDLER
if(!host)
return //How did we even get here
if(hand != host.hand_bodyparts[host.active_hand_index])
return //wrong hand
if(Retract())
return COMSIG_KB_ACTIVATED
/obj/item/organ/internal/cyberimp/arm/proc/Retract()
if(!active_item || (active_item in src))
return FALSE
if(owner)
owner.visible_message(
span_notice("[owner] retracts [active_item] back into [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."),
span_notice("[active_item] snaps back into your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."),
span_hear("You hear a short mechanical noise."),
)
owner.transferItemToLoc(active_item, src, TRUE)
else
active_item.forceMove(src)
UnregisterSignal(active_item, COMSIG_ITEM_ATTACK_SELF)
UnregisterSignal(active_item, COMSIG_ITEM_ATTACK_SELF_SECONDARY)
active_item = null
playsound(get_turf(owner), retract_sound, 50, TRUE)
return TRUE
/obj/item/organ/internal/cyberimp/arm/proc/Extend(obj/item/augment)
if(!(augment in src))
return
active_item = augment
active_item.resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
ADD_TRAIT(active_item, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
active_item.slot_flags = null
active_item.set_custom_materials(null)
var/side = zone == BODY_ZONE_R_ARM? RIGHT_HANDS : LEFT_HANDS
var/hand = owner.get_empty_held_index_for_side(side)
if(hand)
owner.put_in_hand(active_item, hand)
else
var/list/hand_items = owner.get_held_items_for_side(side, all = TRUE)
var/success = FALSE
var/list/failure_message = list()
for(var/i in 1 to hand_items.len) //Can't just use *in* here.
var/hand_item = hand_items[i]
if(!owner.dropItemToGround(hand_item))
failure_message += span_warning("Your [hand_item] interferes with [src]!")
continue
to_chat(owner, span_notice("You drop [hand_item] to activate [src]!"))
success = owner.put_in_hand(active_item, owner.get_empty_held_index_for_side(side))
break
if(!success)
for(var/i in failure_message)
to_chat(owner, i)
return
owner.visible_message(span_notice("[owner] extends [active_item] from [owner.p_their()] [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."),
span_notice("You extend [active_item] from your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm."),
span_hear("You hear a short mechanical noise."))
playsound(get_turf(owner), extend_sound, 50, TRUE)
if(length(items_list) > 1)
RegisterSignals(active_item, list(COMSIG_ITEM_ATTACK_SELF, COMSIG_ITEM_ATTACK_SELF_SECONDARY), PROC_REF(swap_tools)) // secondary for welders
/obj/item/organ/internal/cyberimp/arm/proc/swap_tools(active_item)
SIGNAL_HANDLER
Retract(active_item)
INVOKE_ASYNC(src, PROC_REF(ui_action_click))
/obj/item/organ/internal/cyberimp/arm/ui_action_click()
if((organ_flags & ORGAN_FAILING) || (!active_item && !contents.len))
to_chat(owner, span_warning("The implant doesn't respond. It seems to be broken..."))
return
if(!active_item || (active_item in src))
active_item = null
if(contents.len == 1)
Extend(contents[1])
else
var/list/choice_list = list()
for(var/datum/weakref/augment_ref in items_list)
var/obj/item/augment_item = augment_ref.resolve()
if(!augment_item)
items_list -= augment_ref
continue
choice_list[augment_item] = image(augment_item)
var/obj/item/choice = show_radial_menu(owner, owner, choice_list)
if(owner && owner == usr && owner.stat != DEAD && (src in owner.organs) && !active_item && (choice in contents))
// This monster sanity check is a nice example of how bad input is.
Extend(choice)
else
Retract()
/obj/item/organ/internal/cyberimp/arm/gun/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(prob(30/severity) && owner && !(organ_flags & ORGAN_FAILING))
Retract()
owner.visible_message(span_danger("A loud bang comes from [owner]\'s [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm!"))
playsound(get_turf(owner), 'sound/weapons/flashbang.ogg', 100, TRUE)
to_chat(owner, span_userdanger("You feel an explosion erupt inside your [zone == BODY_ZONE_R_ARM ? "right" : "left"] arm as your implant breaks!"))
owner.adjust_fire_stacks(20)
owner.ignite_mob()
owner.adjustFireLoss(25)
organ_flags |= ORGAN_FAILING
/obj/item/organ/internal/cyberimp/arm/gun/laser
name = "arm-mounted laser implant"
desc = "A variant of the arm cannon implant that fires lethal laser beams. The cannon emerges from the subject's arm and remains inside when not in use."
icon_state = "arm_laser"
items_to_create = list(/obj/item/gun/energy/laser/mounted/augment)
/obj/item/organ/internal/cyberimp/arm/gun/laser/l
zone = BODY_ZONE_L_ARM
/obj/item/organ/internal/cyberimp/arm/gun/taser
name = "arm-mounted taser implant"
desc = "A variant of the arm cannon implant that fires electrodes and disabler shots. The cannon emerges from the subject's arm and remains inside when not in use."
icon_state = "arm_taser"
items_to_create = list(/obj/item/gun/energy/e_gun/advtaser/mounted)
/obj/item/organ/internal/cyberimp/arm/gun/taser/l
zone = BODY_ZONE_L_ARM
/obj/item/organ/internal/cyberimp/arm/toolset
name = "integrated toolset implant"
desc = "A stripped-down version of the engineering cyborg toolset, designed to be installed on subject's arm. Contain advanced versions of every tool."
actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit)
items_to_create = list(
/obj/item/screwdriver/cyborg,
/obj/item/wrench/cyborg,
/obj/item/weldingtool/largetank/cyborg,
/obj/item/crowbar/cyborg,
/obj/item/wirecutters/cyborg,
/obj/item/multitool/cyborg,
)
/obj/item/organ/internal/cyberimp/arm/toolset/l
zone = BODY_ZONE_L_ARM
/obj/item/organ/internal/cyberimp/arm/toolset/emag_act(mob/user, obj/item/card/emag/emag_card)
for(var/datum/weakref/created_item in items_list)
var/obj/potential_knife = created_item.resolve()
if(istype(/obj/item/knife/combat/cyborg, potential_knife))
return FALSE
balloon_alert(user, "integrated knife unlocked")
items_list += WEAKREF(new /obj/item/knife/combat/cyborg(src))
return TRUE
/obj/item/organ/internal/cyberimp/arm/esword
name = "arm-mounted energy blade"
desc = "An illegal and highly dangerous cybernetic implant that can project a deadly blade of concentrated energy."
items_to_create = list(/obj/item/melee/energy/blade/hardlight)
/obj/item/organ/internal/cyberimp/arm/medibeam
name = "integrated medical beamgun"
desc = "A cybernetic implant that allows the user to project a healing beam from their hand."
items_to_create = list(/obj/item/gun/medbeam)
/obj/item/organ/internal/cyberimp/arm/flash
name = "integrated high-intensity photon projector" //Why not
desc = "An integrated projector mounted onto a user's arm that is able to be used as a powerful flash."
items_to_create = list(/obj/item/assembly/flash/armimplant)
/obj/item/organ/internal/cyberimp/arm/flash/Initialize(mapload)
. = ..()
for(var/datum/weakref/created_item in items_list)
var/obj/potential_flash = created_item.resolve()
if(!istype(potential_flash, /obj/item/assembly/flash/armimplant))
continue
var/obj/item/assembly/flash/armimplant/flash = potential_flash
flash.arm = WEAKREF(src)
/obj/item/organ/internal/cyberimp/arm/flash/Extend()
. = ..()
active_item.set_light_range(7)
active_item.set_light_on(TRUE)
/obj/item/organ/internal/cyberimp/arm/flash/Retract()
if(active_item)
active_item.set_light_on(FALSE)
return ..()
/obj/item/organ/internal/cyberimp/arm/baton
name = "arm electrification implant"
desc = "An illegal combat implant that allows the user to administer disabling shocks from their arm."
items_to_create = list(/obj/item/borg/stun)
/obj/item/organ/internal/cyberimp/arm/combat
name = "combat cybernetics implant"
desc = "A powerful cybernetic implant that contains combat modules built into the user's arm."
items_to_create = list(
/obj/item/melee/energy/blade/hardlight,
/obj/item/gun/medbeam,
/obj/item/borg/stun,
/obj/item/assembly/flash/armimplant,
)
/obj/item/organ/internal/cyberimp/arm/combat/Initialize(mapload)
. = ..()
for(var/datum/weakref/created_item in items_list)
var/obj/potential_flash = created_item.resolve()
if(!istype(potential_flash, /obj/item/assembly/flash/armimplant))
continue
var/obj/item/assembly/flash/armimplant/flash = potential_flash
flash.arm = WEAKREF(src)
/obj/item/organ/internal/cyberimp/arm/surgery
name = "surgical toolset implant"
desc = "A set of surgical tools hidden behind a concealed panel on the user's arm."
actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit)
items_to_create = list(
/obj/item/retractor/augment,
/obj/item/hemostat/augment,
/obj/item/cautery/augment,
/obj/item/surgicaldrill/augment,
/obj/item/scalpel/augment,
/obj/item/circular_saw/augment,
/obj/item/surgical_drapes,
)
/obj/item/organ/internal/cyberimp/arm/surgery/emagged
name = "hacked surgical toolset implant"
desc = "A set of surgical tools hidden behind a concealed panel on the user's arm. This one seems to have been tampered with."
items_to_create = list(
/obj/item/retractor/augment,
/obj/item/hemostat/augment,
/obj/item/cautery/augment,
/obj/item/surgicaldrill/augment,
/obj/item/scalpel/augment,
/obj/item/circular_saw/augment,
/obj/item/surgical_drapes,
/obj/item/knife/combat/cyborg,
)
/obj/item/organ/internal/cyberimp/arm/muscle
name = "\proper Strong-Arm empowered musculature implant"
desc = "When implanted, this cybernetic implant will enhance the muscles of the arm to deliver more power-per-action."
icon_state = "muscle_implant"
zone = BODY_ZONE_R_ARM
slot = ORGAN_SLOT_RIGHT_ARM_AUG
actions_types = list()
///The amount of damage the implant adds to our unarmed attacks.
var/punch_damage = 5
///IF true, the throw attack will not smash people into walls
var/non_harmful_throw = TRUE
///How far away your attack will throw your oponent
var/attack_throw_range = 1
///Minimum throw power of the attack
var/throw_power_min = 1
///Maximum throw power of the attack
var/throw_power_max = 4
///How long will the implant malfunction if it is EMP'd
var/emp_base_duration = 9 SECONDS
/obj/item/organ/internal/cyberimp/arm/muscle/on_mob_insert(mob/living/carbon/arm_owner)
. = ..()
if(ishuman(arm_owner)) //Sorry, only humans
RegisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
/obj/item/organ/internal/cyberimp/arm/muscle/on_mob_remove(mob/living/carbon/arm_owner)
. = ..()
UnregisterSignal(arm_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK)
/obj/item/organ/internal/cyberimp/arm/muscle/emp_act(severity)
. = ..()
if((organ_flags & ORGAN_FAILING) || . & EMP_PROTECT_SELF)
return
owner.balloon_alert(owner, "your arm spasms wildly!")
organ_flags |= ORGAN_FAILING
addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity)
/obj/item/organ/internal/cyberimp/arm/muscle/proc/reboot()
organ_flags &= ~ORGAN_FAILING
owner.balloon_alert(owner, "your arm stops spasming!")
/obj/item/organ/internal/cyberimp/arm/muscle/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
if(source.get_active_hand() != hand || !proximity)
return NONE
if(!source.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK))
return NONE
if(!isliving(target))
return NONE
var/datum/dna/dna = source.has_dna()
if(dna?.check_mutation(/datum/mutation/human/hulk)) //NO HULK
return NONE
if(!source.can_unarmed_attack())
return COMPONENT_SKIP_ATTACK
var/mob/living/living_target = target
source.changeNext_move(CLICK_CD_MELEE)
var/picked_hit_type = pick("punch", "smash", "pummel", "bash", "slam")
if(organ_flags & ORGAN_FAILING)
if(source.body_position != LYING_DOWN && living_target != source && prob(50))
to_chat(source, span_danger("You try to [picked_hit_type] [living_target], but lose your balance and fall!"))
source.Knockdown(3 SECONDS)
source.forceMove(get_turf(living_target))
else
to_chat(source, span_danger("Your muscles spasm!"))
source.Paralyze(1 SECONDS)
return COMPONENT_CANCEL_ATTACK_CHAIN
if(ishuman(target))
var/mob/living/carbon/human/human_target = target
if(human_target.check_block(source, punch_damage, "[source]'s' [picked_hit_type]"))
source.do_attack_animation(target)
playsound(living_target.loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1)
log_combat(source, target, "attempted to [picked_hit_type]", "muscle implant")
return COMPONENT_CANCEL_ATTACK_CHAIN
var/potential_damage = punch_damage
var/obj/item/bodypart/attacking_bodypart = hand
potential_damage += rand(attacking_bodypart.unarmed_damage_low, attacking_bodypart.unarmed_damage_high)
source.do_attack_animation(target, ATTACK_EFFECT_SMASH)
playsound(living_target.loc, 'sound/weapons/punch1.ogg', 25, TRUE, -1)
var/target_zone = living_target.get_random_valid_zone(source.zone_selected)
var/armor_block = living_target.run_armor_check(target_zone, MELEE, armour_penetration = attacking_bodypart.unarmed_effectiveness)
living_target.apply_damage(potential_damage, attacking_bodypart.attack_type, target_zone, armor_block)
living_target.apply_damage(potential_damage*1.5, STAMINA, target_zone, armor_block)
if(source.body_position != LYING_DOWN) //Throw them if we are standing
var/atom/throw_target = get_edge_target_turf(living_target, source.dir)
living_target.throw_at(throw_target, attack_throw_range, rand(throw_power_min,throw_power_max), source, gentle = non_harmful_throw)
living_target.visible_message(
span_danger("[source] [picked_hit_type]ed [living_target]!"),
span_userdanger("You're [picked_hit_type]ed by [source]!"),
span_hear("You hear a sickening sound of flesh hitting flesh!"),
COMBAT_MESSAGE_RANGE,
source,
)
to_chat(source, span_danger("You [picked_hit_type] [target]!"))
log_combat(source, target, "[picked_hit_type]ed", "muscle implant")
return COMPONENT_CANCEL_ATTACK_CHAIN