-
-
Notifications
You must be signed in to change notification settings - Fork 444
/
Copy pathstorage.dm
845 lines (731 loc) · 30.9 KB
/
storage.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
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
#define COLLECT_ONE 0
#define COLLECT_EVERYTHING 1
#define COLLECT_SAME 2
#define DROP_NOTHING 0
#define DROP_AT_PARENT 1
#define DROP_AT_LOCATION 2
// External storage-related logic:
// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages
// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement
/datum/component/storage
dupe_mode = COMPONENT_DUPE_UNIQUE
var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point.
var/list/can_hold //if this is set, only items, and their children, will fit
var/list/cant_hold //if this is set, items, and their children, won't fit
var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit.
var/can_hold_description
var/list/mob/is_using //lazy list of mobs looking at the contents of this storage.
var/locked = FALSE //when locked nothing can see inside or use it.
var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit.
var/max_combined_w_class = 14 //max combined sizes of objects that will fit.
var/max_items = 7 //max number of objects that will fit.
var/emp_shielded = FALSE
var/silent = FALSE //whether this makes a message when things are put in.
var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around.
var/rustle_sound = TRUE //play rustle sound on interact.
var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly.
var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile.
var/collection_mode = COLLECT_EVERYTHING
var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray.
var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.
var/atom/movable/screen/storage/boxes //storage display object
var/atom/movable/screen/close/closer //close button object
var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.
var/attack_hand_interact = TRUE //interact on attack hand.
var/quickdraw = FALSE //right click interact
var/datum/weakref/modeswitch_action_ref
//Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future.
var/screen_max_columns = 7 //These two determine maximum screen sizes.
var/screen_max_rows = INFINITY
var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer
var/screen_pixel_y = 16
var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise.
var/screen_start_y = 2
//End
/datum/component/storage/Initialize(datum/component/storage/concrete/master)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
if(master)
change_master(master)
boxes = new(null, src)
closer = new(null, src)
orient2hud()
RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, PROC_REF(on_check))
RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, PROC_REF(check_locked))
RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, PROC_REF(signal_show_attempt))
RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, PROC_REF(signal_insertion_attempt))
RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, PROC_REF(signal_can_insert))
RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, PROC_REF(signal_take_type))
RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, PROC_REF(signal_fill_type))
RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, PROC_REF(set_locked))
RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, PROC_REF(signal_take_obj))
RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, PROC_REF(signal_quick_empty))
RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, PROC_REF(signal_hide_attempt))
RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, PROC_REF(close_all))
RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, PROC_REF(signal_return_inv))
RegisterSignal(parent, COMSIG_TOPIC, PROC_REF(topic_handle))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(attackby))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(show_to_ghost))
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(refresh_mob_views))
RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(_remove_and_refresh))
RegisterSignal(parent, COMSIG_ATOM_CANREACH, PROC_REF(canreach_react))
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(preattack_intercept))
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(attack_self))
RegisterSignal(parent, COMSIG_ITEM_PICKUP, PROC_REF(signal_on_pickup))
RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, PROC_REF(close_all))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
RegisterSignals(parent, list( \
COMSIG_CLICK_ALT, \
COMSIG_ATOM_ATTACK_HAND_SECONDARY, \
COMSIG_ITEM_ATTACK_SELF_SECONDARY, \
), PROC_REF(on_open_storage_click))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY_SECONDARY, PROC_REF(on_open_storage_attackby))
RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, PROC_REF(mousedrop_onto))
RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive))
update_actions()
/datum/component/storage/Destroy()
close_all()
QDEL_NULL(boxes)
QDEL_NULL(closer)
LAZYCLEARLIST(is_using)
return ..()
/datum/component/storage/PreTransfer()
update_actions()
/// Almost 100% of the time the lists passed into set_holdable are reused for each instance of the component
/// Just fucking cache it 4head
/// Yes I could generalize this, but I don't want anyone else using it. in fact, DO NOT COPY THIS
/// If you find yourself needing this pattern, you're likely better off using static typecaches
/// I'm not because I do not trust implementers of the storage component to use them, BUT
/// IF I FIND YOU USING THIS PATTERN IN YOUR CODE I WILL BREAK YOU ACROSS MY KNEES
/// ~Lemon
GLOBAL_LIST_EMPTY(cached_storage_typecaches)
/datum/component/storage/proc/set_holdable(list/can_hold_list, list/cant_hold_list)
if(!islist(can_hold_list))
can_hold_list = list(can_hold_list)
if(!islist(cant_hold_list))
cant_hold_list = list(cant_hold_list)
can_hold_description = generate_hold_desc(can_hold_list)
if (can_hold_list)
var/unique_key = can_hold_list.Join("-")
if(!GLOB.cached_storage_typecaches[unique_key])
GLOB.cached_storage_typecaches[unique_key] = typecacheof(can_hold_list)
can_hold = GLOB.cached_storage_typecaches[unique_key]
if (cant_hold_list != null)
var/unique_key = cant_hold_list.Join("-")
if(!GLOB.cached_storage_typecaches[unique_key])
GLOB.cached_storage_typecaches[unique_key] = typecacheof(cant_hold_list)
cant_hold = GLOB.cached_storage_typecaches[unique_key]
/datum/component/storage/proc/generate_hold_desc(can_hold_list)
var/list/desc = list()
for(var/valid_type in can_hold_list)
var/obj/item/valid_item = valid_type
desc += "\a [initial(valid_item.name)]"
return "\n\t[span_notice("[desc.Join("\n\t")]")]"
/datum/component/storage/proc/update_actions()
if(!isitem(parent) || !allow_quick_gather)
QDEL_NULL(modeswitch_action_ref)
return
var/datum/action/existing = modeswitch_action_ref?.resolve()
if(!QDELETED(existing))
return
var/obj/item/item_parent = parent
var/datum/action/modeswitch_action = item_parent.add_item_action(/datum/action/item_action/storage_gather_mode)
RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, PROC_REF(action_trigger))
modeswitch_action_ref = WEAKREF(modeswitch_action)
/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master)
if(new_master == src || (!isnull(new_master) && !istype(new_master)))
return FALSE
if(master)
master.on_slave_unlink(src)
master = new_master
if(master)
master.on_slave_link(src)
return TRUE
/datum/component/storage/proc/master()
if(master == src)
return //infinite loops yo.
return master
/datum/component/storage/proc/real_location()
var/datum/component/storage/concrete/master = master()
return master? master.real_location() : null
/datum/component/storage/proc/canreach_react(datum/source, list/next)
var/datum/component/storage/concrete/master = master()
if(!master)
return
. = COMPONENT_BLOCK_REACH
next += master.parent
for(var/i in master.slaves)
var/datum/component/storage/slave = i
next += slave.parent
/datum/component/storage/proc/on_move()
var/atom/A = parent
for(var/mob/living/L in can_see_contents())
if(!L.CanReach(A))
hide_from(L)
/datum/component/storage/proc/attack_self(datum/source, mob/user, modifiers)
if(locked)
to_chat(user, span_warning("[parent] seems to be locked!"))
return FALSE
if((user.get_active_held_item() == parent) && allow_quick_empty)
quick_empty(user)
/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params)
if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE))
return FALSE
. = COMPONENT_NO_ATTACK
if(locked)
to_chat(M, span_warning("[parent] seems to be locked!"))
return FALSE
var/obj/item/I = O
if(collection_mode == COLLECT_ONE)
if(can_be_inserted(I, null, M))
handle_item_insertion(I, null, M)
return
if(!isturf(I.loc))
return
var/list/things = I.loc.contents.Copy()
if(collection_mode == COLLECT_SAME)
things = typecache_filter_list(things, typecacheof(I.type))
var/len = length(things)
if(!len)
to_chat(M, span_notice("You failed to pick up anything with [parent]."))
return
var/list/rejections = list()
while(do_after(M, 1 SECONDS, parent, NONE, TRUE, CALLBACK(src, PROC_REF(handle_mass_pickup), things, I.loc, rejections)))
continue
to_chat(M, span_notice("You put everything you could [insert_preposition] [parent]."))
/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user)
var/atom/source_real_location = src_object.real_location()
for(var/obj/item/I in things)
things -= I
if(I.loc != source_real_location)
continue
if(user.active_storage != src_object)
if(I.on_found(user))
break
if(can_be_inserted(I,FALSE,user))
handle_item_insertion(I, TRUE, user)
if (TICK_CHECK)
return TRUE
return FALSE
/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections)
var/atom/real_location = real_location()
for(var/obj/item/I in things)
things -= I
if(I.loc != thing_loc)
continue
if(I.type in rejections) // To limit bag spamming: any given type only complains once
continue
if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no
if(real_location.contents.len >= max_items)
break
rejections += I.type // therefore full bags are still a little spammy
continue
handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed.
if (TICK_CHECK)
return TRUE
return FALSE
/datum/component/storage/proc/quick_empty(mob/M)
var/atom/A = parent
if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated())
return
if(locked)
to_chat(M, span_warning("[parent] seems to be locked!"))
return FALSE
A.add_fingerprint(M)
to_chat(M, span_notice("You start dumping out [parent]."))
var/turf/T = get_turf(A)
var/list/things = contents()
while (do_after(M, 1 SECONDS, T, NONE, TRUE, CALLBACK(src, PROC_REF(mass_remove_from_storage), T, things)))
continue
/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, trigger_on_found = TRUE)
var/atom/real_location = real_location()
for(var/obj/item/I in things)
things -= I
if(I.loc != real_location)
continue
remove_from_storage(I, target)
if(trigger_on_found && I.on_found())
return FALSE
if(TICK_CHECK)
return TRUE
return FALSE
/datum/component/storage/proc/do_quick_empty(atom/_target)
if(!_target)
_target = get_turf(parent)
if(usr)
hide_from(usr)
var/list/contents = contents()
var/atom/real_location = real_location()
for(var/obj/item/I in contents)
if(I.loc != real_location)
continue
remove_from_storage(I, _target)
return TRUE
/datum/component/storage/proc/set_locked(datum/source, new_state)
locked = new_state
if(locked)
close_all()
/datum/component/storage/proc/_process_numerical_display()
. = list()
var/atom/real_location = real_location()
for(var/obj/item/I in real_location.contents)
if(QDELETED(I))
continue
if(!.["[I.type]-[I.name]"])
.["[I.type]-[I.name]"] = new /datum/numbered_display(I, 1)
else
var/datum/numbered_display/ND = .["[I.type]-[I.name]"]
ND.number++
//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing.
/datum/component/storage/proc/orient2hud()
var/atom/real_location = real_location()
var/adjusted_contents = real_location.contents.len
//Numbered contents display
var/list/datum/numbered_display/numbered_contents
if(display_numerical_stacking)
numbered_contents = _process_numerical_display()
adjusted_contents = numbered_contents.len
var/columns = clamp(max_items, 1, screen_max_columns)
var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
standard_orient_objs(rows, columns, numbered_contents)
//This proc draws out the inventory and places the items on it. It uses the standard position.
/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents)
boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
var/cx = screen_start_x
var/cy = screen_start_y
if(islist(numerical_display_contents))
for(var/type in numerical_display_contents)
var/datum/numbered_display/ND = numerical_display_contents[type]
ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
ND.sample_object.maptext = "<font color='white'>[(ND.number > 1)? "[ND.number]" : ""]</font>"
ND.sample_object.plane = ABOVE_HUD_PLANE
cx++
if(cx - screen_start_x >= cols)
cx = screen_start_x
cy++
if(cy - screen_start_y >= rows)
break
else
var/atom/real_location = real_location()
for(var/obj/O in real_location)
if(QDELETED(O))
continue
O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
O.maptext = ""
O.plane = ABOVE_HUD_PLANE
cx++
if(cx - screen_start_x >= cols)
cx = screen_start_x
cy++
if(cy - screen_start_y >= rows)
break
closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]"
/datum/component/storage/proc/show_to(mob/M)
if(!M.client)
return FALSE
var/atom/real_location = real_location()
if(M.active_storage != src && (M.stat == CONSCIOUS))
for(var/obj/item/I in real_location)
if(I.on_found(M))
return FALSE
if(M.active_storage)
M.active_storage.hide_from(M)
orient2hud()
M.client.screen |= boxes
M.client.screen |= closer
M.client.screen |= real_location.contents
M.active_storage = src
LAZYOR(is_using, M)
return TRUE
/datum/component/storage/proc/hide_from(mob/M)
if(!M.client)
return TRUE
var/atom/real_location = real_location()
M.client.screen -= boxes
M.client.screen -= closer
M.client.screen -= real_location.contents
if(M.active_storage == src)
M.active_storage = null
LAZYREMOVE(is_using, M)
return TRUE
/datum/component/storage/proc/close(mob/M)
hide_from(M)
/datum/component/storage/proc/close_all()
. = FALSE
for(var/mob/M in can_see_contents())
close(M)
. = TRUE //returns TRUE if any mobs actually got a close(M) call
/datum/component/storage/proc/emp_act(datum/source, severity)
if(emp_shielded)
return
var/datum/component/storage/concrete/master = master()
master.emp_act(source, severity)
//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right.
//The numbers are calculated from the bottom-left The bottom-left slot being 1,1.
/datum/component/storage/proc/orient_objs(tx, ty, mx, my)
var/atom/real_location = real_location()
var/cx = tx
var/cy = ty
boxes.screen_loc = "[tx]:,[ty] to [mx],[my]"
for(var/obj/O in real_location)
if(QDELETED(O))
continue
O.screen_loc = "[cx],[cy]"
O.plane = ABOVE_HUD_PLANE
cx++
if(cx > mx)
cx = tx
cy--
closer.screen_loc = "[mx+1],[my]"
//Resets something that is being removed from storage.
/datum/component/storage/proc/_removal_reset(atom/movable/thing)
if(!istype(thing))
return FALSE
var/datum/component/storage/concrete/master = master()
if(!istype(master))
return FALSE
return master._removal_reset(thing)
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
_removal_reset(thing)
refresh_mob_views()
//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted
/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location)
if(!istype(AM))
return FALSE
var/datum/component/storage/concrete/master = master()
if(!istype(master))
return FALSE
return master.remove_from_storage(AM, new_location)
/datum/component/storage/proc/refresh_mob_views()
var/list/seeing = can_see_contents()
for(var/i in seeing)
show_to(i)
return TRUE
/datum/component/storage/proc/can_see_contents()
var/list/cansee = list()
for(var/mob/M in is_using)
if(M.active_storage == src && M.client)
cansee |= M
else
LAZYREMOVE(is_using, M)
return cansee
//Tries to dump content
/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M)
var/atom/A = parent
var/atom/dump_destination = dest_object.get_dumping_location()
if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination))
if(locked)
to_chat(M, span_warning("[parent] seems to be locked!"))
return FALSE
if(dump_destination.storage_contents_dump_act(src, M))
playsound(A, "rustle", 50, 1, -5)
return TRUE
return FALSE
//This proc is called when you want to place an item into the storage item.
/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params)
if(istype(I, /obj/item/hand_labeler))
var/obj/item/hand_labeler/labeler = I
if(labeler.mode)
return FALSE
. = TRUE //no afterattack
if(iscyborg(M))
return
if(!can_be_inserted(I, FALSE, M))
var/atom/real_location = real_location()
if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit
return TRUE
return FALSE
handle_item_insertion(I, FALSE, M)
/datum/component/storage/proc/return_inv(recursive)
var/list/ret = list()
ret |= contents()
if(recursive)
for(var/i in ret.Copy())
var/atom/A = i
SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE)
return ret
/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS!
var/atom/real_location = real_location()
return real_location.contents.Copy()
//Abuses the fact that lists are just references, or something like that.
/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE)
if(!islist(interface))
return FALSE
interface |= return_inv(recursive)
return TRUE
/datum/component/storage/proc/topic_handle(datum/source, user, href_list)
if(href_list["show_valid_pocket_items"])
handle_show_valid_items(source, user)
/datum/component/storage/proc/handle_show_valid_items(datum/source, user)
to_chat(user, span_notice("[source] can hold: [can_hold_description]"))
/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M)
set waitfor = FALSE
. = COMPONENT_NO_MOUSEDROP
var/atom/A = parent
if(isliving(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked //yogs -- Makes ghosts not be able to interact with storage shit
A.add_fingerprint(M)
if(!over_object)
return FALSE
if(ismecha(M.loc)) // stops inventory actions in a mech
return FALSE
if(ismouse(M) && (locate(/obj/structure/table) in get_turf(parent))) // Prevents mice using storages on tables
return FALSE
// this must come before the screen objects only block, dunno why it wasn't before
if(over_object == M)
user_show_to_mob(M)
if(!M.incapacitated())
if(!istype(over_object, /atom/movable/screen))
dump_content_at(over_object, M)
return
if(A.loc != M)
return
playsound(A, "rustle", 50, 1, -5)
if(istype(over_object, /atom/movable/screen/inventory/hand))
var/atom/movable/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(A, H.held_index)
return
A.add_fingerprint(M)
/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE)
var/atom/A = parent
if(!istype(M))
return FALSE
A.add_fingerprint(M)
if(locked && !force)
to_chat(M, span_warning("[parent] seems to be locked!"))
return FALSE
if(force || M.CanReach(parent, view_only = TRUE))
show_to(M)
/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M)
if(isitem(O))
var/obj/item/I = O
if(iscarbon(M) || isdrone(M))
var/mob/living/L = M
if(!L.incapacitated() && I == L.get_active_held_item())
if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert.
handle_item_insertion(I, FALSE, L)
//This proc return 1 if the item can be picked up and 0 if it can't.
//Set the stop_messages to stop it from printing messages
/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M)
if(!istype(I) || (I.item_flags & ABSTRACT))
return FALSE //Not an item
if(I == parent)
return FALSE //no paradoxes for you
var/atom/real_location = real_location()
var/atom/host = parent
if(real_location == I.loc)
return FALSE //Means the item is already in the storage item
if(locked)
if(M && !stop_messages)
host.add_fingerprint(M)
to_chat(M, span_warning("[host] seems to be locked!"))
return FALSE
if(real_location.contents.len >= max_items)
if(!stop_messages)
to_chat(M, span_warning("[host] is full, make some space!"))
return FALSE //Storage item is full
if(length(can_hold))
if(!is_type_in_typecache(I, can_hold))
if(!stop_messages)
to_chat(M, span_warning("[host] cannot hold [I]!"))
return FALSE
if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold.
if(!stop_messages)
to_chat(M, span_warning("[host] cannot hold [I]!"))
return FALSE
if(I.w_class > max_w_class && !is_type_in_typecache(I, exception_hold))
if(!stop_messages)
to_chat(M, span_warning("[I] is too big for [host]!"))
return FALSE
var/sum_w_class = I.w_class
for(var/obj/item/_I in real_location)
sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it.
if(sum_w_class > max_combined_w_class)
if(!stop_messages)
to_chat(M, span_warning("[I] won't fit in [host], make some space!"))
return FALSE
if(isitem(host))
var/obj/item/IP = host
var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage)
if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting)
if(!stop_messages)
to_chat(M, span_warning("[IP] cannot hold [I] as it's a storage item of the same size!"))
return FALSE //To prevent the stacking of same sized storage items.
if(HAS_TRAIT(I, TRAIT_NO_STORAGE))
if(!stop_messages)
to_chat(M, span_warning("\the [I] can't seem to fit in \the [host]!"))
return FALSE
if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry.
if(!stop_messages)
to_chat(M, span_warning("\the [I] is stuck to your hand, you can't put it in \the [host]!"))
return FALSE
var/datum/component/storage/concrete/master = master()
if(!istype(master))
return FALSE
return master.slave_can_insert_object(src, I, stop_messages, M)
/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE)
return FALSE
//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted()
//The prevent_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once,
//such as when picking up all the items on a tile with one click.
/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote)
var/atom/parent = src.parent
var/datum/component/storage/concrete/master = master()
if(!istype(master))
return FALSE
if(silent)
prevent_warning = TRUE
if(M)
parent.add_fingerprint(M)
. = master.handle_item_insertion_from_slave(src, I, prevent_warning, M)
/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE)
if(silent && !override)
return
if(rustle_sound)
playsound(parent, "rustle", 50, 1, -5)
for(var/mob/viewing in viewers(user, null))
if(M == viewing)
to_chat(usr, span_notice("You put [I] [insert_preposition]to [parent]."))
else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
viewing.show_message(span_notice("[M] puts [I] [insert_preposition]to [parent]."), MSG_VISUAL)
else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
viewing.show_message(span_notice("[M] puts [I] [insert_preposition]to [parent]."), MSG_VISUAL)
/datum/component/storage/proc/update_icon()
SHOULD_CALL_PARENT(TRUE)
if(isobj(parent))
var/obj/O = parent
O.update_appearance(UPDATE_ICON)
/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE)
if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent))
return FALSE
return handle_item_insertion(I, silent, M)
/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE)
return can_be_inserted(I, silent, M)
/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M)
return user_show_to_mob(M, TRUE)
/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE)
return user_show_to_mob(showto, force)
/datum/component/storage/proc/on_check()
return TRUE
/datum/component/storage/proc/check_locked()
return locked
/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted)
if(!force)
if(check_adjacent)
if(!user || !user.CanReach(destination) || !user.CanReach(parent))
return FALSE
var/list/taking = typecache_filter_list(contents(), typecacheof(type))
if(taking.len > amount)
taking.len = amount
if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item.
for(var/i in taking)
if(remove_from_storage(i, destination))
inserted |= i
else
for(var/i in taking)
remove_from_storage(i, destination)
return TRUE
/datum/component/storage/proc/remaining_space_items()
var/atom/real_location = real_location()
return max(0, max_items - real_location.contents.len)
/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE)
var/atom/real_location = real_location()
if(!force)
amount = min(remaining_space_items(), amount)
for(var/i in 1 to amount)
handle_item_insertion(new type(real_location), TRUE)
CHECK_TICK
return TRUE
/datum/component/storage/proc/on_attack_hand(datum/source, mob/user, modifiers)
var/atom/A = parent
if(!attack_hand_interact)
return
if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item
user.active_storage.close(user)
close(user)
. = COMPONENT_NO_ATTACK_HAND
return
if(rustle_sound)
playsound(A, "rustle", 50, 1, -5)
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket.
. = COMPONENT_NO_ATTACK_HAND
H.put_in_hands(A)
H.l_store = null
return
if(H.r_store == A && !H.get_active_held_item())
. = COMPONENT_NO_ATTACK_HAND
H.put_in_hands(A)
H.r_store = null
return
if(A.loc == user)
. = COMPONENT_NO_ATTACK_HAND
if(locked)
to_chat(user, span_warning("[parent] seems to be locked!"))
else
show_to(user)
/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user)
var/atom/A = parent
update_actions()
for(var/mob/M in range(1, A))
if(M.active_storage == src)
close(M)
/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE)
if(!(AM in real_location()))
return FALSE
return remove_from_storage(AM, new_loc)
/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget)
return do_quick_empty(loctarget)
/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target)
return hide_from(target)
/datum/component/storage/proc/open_storage(mob/user)
if(!isliving(user) || !user.CanReach(parent))
return FALSE
if(locked)
if(istype(parent, /obj/item/storage/lockbox))
return FALSE
to_chat(user, span_warning("[parent] seems to be locked!"))
return FALSE
. = TRUE
var/atom/A = parent
if(!quickdraw)
A.add_fingerprint(user)
user_show_to_mob(user)
playsound(A, "rustle", 50, 1, -5)
return
if(!user.incapacitated())
var/obj/item/I = locate() in real_location()
if(!I)
return
A.add_fingerprint(user)
remove_from_storage(I, get_turf(user))
if(!user.put_in_hands(I))
to_chat(user, span_notice("You fumble for [I] and it falls on the floor."))
return
user.visible_message(span_warning("[user] draws [I] from [parent]!"), span_notice("You draw [I] from [parent]."))
return
/datum/component/storage/proc/on_open_storage_click(datum/source, mob/user, list/modifiers)
// SIGNAL_HANDLER uncomment at your own peril
if(open_storage(user))
return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/component/storage/proc/on_open_storage_attackby(datum/source, obj/item/weapon, mob/user, params)
// SIGNAL_HANDLER
if(open_storage(user))
return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN
/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source)
gather_mode_switch(source.owner)
return COMPONENT_ACTION_BLOCK_TRIGGER
/datum/component/storage/proc/gather_mode_switch(mob/user)
collection_mode = (collection_mode+1)%3
switch(collection_mode)
if(COLLECT_SAME)
to_chat(user, "[parent] now picks up all items of a single type at once.")
if(COLLECT_EVERYTHING)
to_chat(user, "[parent] now picks up all items in a tile at once.")
if(COLLECT_ONE)
to_chat(user, "[parent] now picks up one item at a time.")