-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
clothing.dm
460 lines (406 loc) · 19.8 KB
/
clothing.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
/obj/item/clothing
name = "clothing"
resistance_flags = FLAMMABLE
max_integrity = 200
integrity_failure = 0.4
var/damaged_clothes = CLOTHING_PRISTINE //similar to machine's BROKEN stat and structure's broken var
///What level of bright light protection item has.
var/flash_protect = FLASH_PROTECTION_NONE
var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect
var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets
var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down
var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv
var/visor_flags_cover = 0 //same as above, but for flags_cover
//what to toggle when toggled with weldingvisortoggle()
var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW
lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
var/alt_desc = null
var/toggle_message = null
var/alt_toggle_message = null
var/active_sound = null
var/toggle_cooldown = null
var/cooldown = 0
var/clothing_flags = NONE
/// What items can be consumed to repair this clothing (must by an /obj/item/stack)
var/repairable_by = /obj/item/stack/sheet/cloth
//Var modification - PLEASE be careful with this I know who you are and where you live
var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts"
var/list/user_vars_remembered //Auto built by the above + dropped() + equipped()
var/pocket_storage_component_path
//These allow head/mask items to dynamically alter the user's hair
// and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi
// for a state matching hair_state+dynamic_hair_suffix
// THESE OVERRIDE THE HIDEHAIR FLAGS
var/dynamic_hair_suffix = ""//head > mask for head hair
var/dynamic_fhair_suffix = ""//mask > head for facial hair
///These are armor values that protect the wearer, taken from the clothing's armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic().
var/list/armor_list = list()
///These are armor values that protect the clothing, taken from its armor datum. List updates on examine because it's currently only used to print armor ratings to chat in Topic().
var/list/durability_list = list()
/// How much clothing damage has been dealt to each of the limbs of the clothing, assuming it covers more than one limb
var/list/damage_by_parts
/// How much integrity is in a specific limb before that limb is disabled (for use in [/obj/item/clothing/proc/take_damage_zone], and only if we cover multiple zones.) Set to 0 to disable shredding.
var/limb_integrity = 0
/// How many zones (body parts, not precise) we have disabled so far, for naming purposes
var/zones_disabled
/obj/item/clothing/Initialize()
if((clothing_flags & VOICEBOX_TOGGLABLE))
actions_types += /datum/action/item_action/toggle_voice_box
. = ..()
if(ispath(pocket_storage_component_path))
LoadComponent(pocket_storage_component_path)
/obj/item/clothing/MouseDrop(atom/over_object)
. = ..()
var/mob/M = usr
if(ismecha(M.loc)) // stops inventory actions in a mech
return
if(!M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand))
var/obj/screen/inventory/hand/H = over_object
if(M.putItemFromInventoryInHandIfPossible(src, H.held_index))
add_fingerprint(usr)
/obj/item/reagent_containers/food/snacks/clothing
name = "temporary moth clothing snack item"
desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object."
list_reagents = list(/datum/reagent/consumable/nutriment = 1)
tastes = list("dust" = 1, "lint" = 1)
foodtype = CLOTH
/obj/item/clothing/attack(mob/M, mob/user, def_zone)
if(user.a_intent != INTENT_HARM && ismoth(M))
if(damaged_clothes == CLOTHING_SHREDDED)
to_chat(user, "<span class='notice'>[src] seem[p_s()] pretty torn apart... [p_they(TRUE)] probably wouldn't be too tasty.</span>")
return
var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new
clothing_as_food.name = name
if(clothing_as_food.attack(M, user, def_zone))
take_damage(15, sound_effect=FALSE)
qdel(clothing_as_food)
else
return ..()
/obj/item/clothing/attackby(obj/item/W, mob/user, params)
if(damaged_clothes && istype(W, repairable_by))
var/obj/item/stack/S = W
switch(damaged_clothes)
if(CLOTHING_DAMAGED)
S.use(1)
repair(user, params)
if(CLOTHING_SHREDDED)
if(S.amount < 3)
to_chat(user, "<span class='warning'>You require 3 [S.name] to repair [src].</span>")
return
to_chat(user, "<span class='notice'>You begin fixing the damage to [src] with [S]...</span>")
if(do_after(user, 6 SECONDS, TRUE, src))
if(S.use(3))
repair(user, params)
return 1
return ..()
/// Set the clothing's integrity back to 100%, remove all damage to bodyparts, and generally fix it up
/obj/item/clothing/proc/repair(mob/user, params)
update_clothes_damaged_state(CLOTHING_PRISTINE)
obj_integrity = max_integrity
name = initial(name) // remove "tattered" or "shredded" if there's a prefix
body_parts_covered = initial(body_parts_covered)
slot_flags = initial(slot_flags)
damage_by_parts = null
if(user)
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
to_chat(user, "<span class='notice'>You fix the damage on [src].</span>")
/**
* take_damage_zone() is used for dealing damage to specific bodyparts on a worn piece of clothing, meant to be called from [/obj/item/bodypart/proc/check_woundings_mods()]
*
* This proc only matters when a bodypart that this clothing is covering is harmed by a direct attack (being on fire or in space need not apply), and only if this clothing covers
* more than one bodypart to begin with. No point in tracking damage by zone for a hat, and I'm not cruel enough to let you fully break them in a few shots.
* Also if limb_integrity is 0, then this clothing doesn't have bodypart damage enabled so skip it.
*
* Arguments:
* * def_zone: The bodypart zone in question
* * damage_amount: Incoming damage
* * damage_type: BRUTE or BURN
* * armour_penetration: If the attack had armour_penetration
*/
/obj/item/clothing/proc/take_damage_zone(def_zone, damage_amount, damage_type, armour_penetration)
if(!def_zone || !limb_integrity || (initial(body_parts_covered) in GLOB.bitflags)) // the second check sees if we only cover one bodypart anyway and don't need to bother with this
return
var/list/covered_limbs = body_parts_covered2organ_names(body_parts_covered) // what do we actually cover?
if(!(def_zone in covered_limbs))
return
var/damage_dealt = take_damage(damage_amount * 0.1, damage_type, armour_penetration, FALSE) * 10 // only deal 10% of the damage to the general integrity damage, then multiply it by 10 so we know how much to deal to limb
LAZYINITLIST(damage_by_parts)
damage_by_parts[def_zone] += damage_dealt
if(damage_by_parts[def_zone] > limb_integrity)
disable_zone(def_zone, damage_type)
/**
* disable_zone() is used to disable a given bodypart's protection on our clothing item, mainly from [/obj/item/clothing/proc/take_damage_zone()]
*
* This proc disables all protection on the specified bodypart for this piece of clothing: it'll be as if it doesn't cover it at all anymore (because it won't!)
* If every possible bodypart has been disabled on the clothing, we put it out of commission entirely and mark it as shredded, whereby it will have to be repaired in
* order to equip it again. Also note we only consider it damaged if there's more than one bodypart disabled.
*
* Arguments:
* * def_zone: The bodypart zone we're disabling
* * damage_type: Only really relevant for the verb for describing the breaking, and maybe obj_destruction()
*/
/obj/item/clothing/proc/disable_zone(def_zone, damage_type)
var/list/covered_limbs = body_parts_covered2organ_names(body_parts_covered)
if(!(def_zone in covered_limbs))
return
var/zone_name = parse_zone(def_zone)
var/break_verb = ((damage_type == BRUTE) ? "torn" : "burned")
if(iscarbon(loc))
var/mob/living/carbon/C = loc
C.visible_message("<span class='danger'>The [zone_name] on [C]'s [src.name] is [break_verb] away!</span>", "<span class='userdanger'>The [zone_name] on your [src.name] is [break_verb] away!</span>", vision_distance = COMBAT_MESSAGE_RANGE)
RegisterSignal(C, COMSIG_MOVABLE_MOVED, .proc/bristle)
zones_disabled++
for(var/i in zone2body_parts_covered(def_zone))
body_parts_covered &= ~i
if(body_parts_covered == NONE) // if there are no more parts to break then the whole thing is kaput
obj_destruction((damage_type == BRUTE ? "melee" : "laser")) // melee/laser is good enough since this only procs from direct attacks anyway and not from fire/bombs
return
switch(zones_disabled)
if(1)
name = "damaged [initial(name)]"
if(2)
name = "mangy [initial(name)]"
if(3 to INFINITY) // take better care of your shit, dude
name = "tattered [initial(name)]"
update_clothes_damaged_state(CLOTHING_DAMAGED)
/obj/item/clothing/Destroy()
user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up
return ..()
/obj/item/clothing/dropped(mob/user)
..()
if(!istype(user))
return
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
if(LAZYLEN(user_vars_remembered))
for(var/variable in user_vars_remembered)
if(variable in user.vars)
if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it)
user.vars[variable] = user_vars_remembered[variable]
user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null.
/obj/item/clothing/equipped(mob/user, slot)
..()
if (!istype(user))
return
if(slot_flags & slot) //Was equipped to a valid slot for this item?
if(iscarbon(user) && LAZYLEN(zones_disabled))
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/bristle)
if (LAZYLEN(user_vars_to_edit))
for(var/variable in user_vars_to_edit)
if(variable in user.vars)
LAZYSET(user_vars_remembered, variable, user.vars[variable])
user.vv_edit_var(variable, user_vars_to_edit[variable])
/obj/item/clothing/examine(mob/user)
. = ..()
if(damaged_clothes == CLOTHING_SHREDDED)
. += "<span class='warning'><b>[p_theyre(TRUE)] completely shredded and require[p_s()] mending before [p_they()] can be worn again!</b></span>"
return
switch (max_heat_protection_temperature)
if (400 to 1000)
. += "[src] offers the wearer limited protection from fire."
if (1001 to 1600)
. += "[src] offers the wearer some protection from fire."
if (1601 to 35000)
. += "[src] offers the wearer robust protection from fire."
for(var/zone in damage_by_parts)
var/pct_damage_part = damage_by_parts[zone] / limb_integrity * 100
var/zone_name = parse_zone(zone)
switch(pct_damage_part)
if(100 to INFINITY)
. += "<span class='warning'><b>The [zone_name] is useless and requires mending!</b></span>"
if(60 to 99)
. += "<span class='warning'>The [zone_name] is heavily shredded!</span>"
if(30 to 59)
. += "<span class='danger'>The [zone_name] is partially shredded.</span>"
var/datum/component/storage/pockets = GetComponent(/datum/component/storage)
if(pockets)
var/list/how_cool_are_your_threads = list("<span class='notice'>")
if(pockets.attack_hand_interact)
how_cool_are_your_threads += "[src]'s storage opens when clicked.\n"
else
how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n"
if (pockets.can_hold?.len) // If pocket type can hold anything, vs only specific items
how_cool_are_your_threads += "[src] can store [pockets.max_items] <a href='?src=[REF(src)];show_valid_pocket_items=1'>item\s</a>.\n"
else
how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s that are [weightclass2text(pockets.max_w_class)] or smaller.\n"
if(pockets.quickdraw)
how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n"
if(pockets.silent)
how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n"
how_cool_are_your_threads += "</span>"
. += how_cool_are_your_threads.Join()
if(LAZYLEN(armor_list))
armor_list.Cut()
if(armor.bio)
armor_list += list("TOXIN" = armor.bio)
if(armor.bomb)
armor_list += list("EXPLOSIVE" = armor.bomb)
if(armor.bullet)
armor_list += list("BULLET" = armor.bullet)
if(armor.energy)
armor_list += list("ENERGY" = armor.energy)
if(armor.laser)
armor_list += list("LASER" = armor.laser)
if(armor.magic)
armor_list += list("MAGIC" = armor.magic)
if(armor.melee)
armor_list += list("MELEE" = armor.melee)
if(armor.rad)
armor_list += list("RADIATION" = armor.rad)
if(LAZYLEN(durability_list))
durability_list.Cut()
if(armor.fire)
durability_list += list("FIRE" = armor.fire)
if(armor.acid)
durability_list += list("ACID" = armor.acid)
if(LAZYLEN(armor_list) || LAZYLEN(durability_list))
. += "<span class='notice'>It has a <a href='?src=[REF(src)];list_armor=1'>tag</a> listing its protection classes.</span>"
/obj/item/clothing/Topic(href, href_list)
. = ..()
if(href_list["list_armor"])
var/list/readout = list("<span class='notice'><u><b>PROTECTION CLASSES (I-X)</u></b>")
if(LAZYLEN(armor_list))
readout += "\n<b>ARMOR</b>"
for(var/dam_type in armor_list)
var/armor_amount = armor_list[dam_type]
readout += "\n[dam_type] [armor_to_protection_class(armor_amount)]" //e.g. BOMB IV
if(LAZYLEN(durability_list))
readout += "\n<b>DURABILITY</b>"
for(var/dam_type in durability_list)
var/durability_amount = durability_list[dam_type]
readout += "\n[dam_type] [armor_to_protection_class(durability_amount)]" //e.g. FIRE II
readout += "</span>"
to_chat(usr, "[readout.Join()]")
/**
* Rounds armor_value to nearest 10, divides it by 10 and then expresses it in roman numerals up to 10
*
* Rounds armor_value to nearest 10, divides it by 10
* and then expresses it in roman numerals up to 10
* Arguments:
* * armor_value - Number we're converting
*/
/obj/item/clothing/proc/armor_to_protection_class(armor_value)
armor_value = round(armor_value,10) / 10
switch (armor_value)
if (1)
. = "I"
if (2)
. = "II"
if (3)
. = "III"
if (4)
. = "IV"
if (5)
. = "V"
if (6)
. = "VI"
if (7)
. = "VII"
if (8)
. = "VIII"
if (9)
. = "IX"
if (10 to INFINITY)
. = "X"
return .
/obj/item/clothing/obj_break(damage_flag)
update_clothes_damaged_state(CLOTHING_DAMAGED)
if(isliving(loc)) //It's not important enough to warrant a message if it's not on someone
var/mob/living/M = loc
if(src in M.get_equipped_items(FALSE))
to_chat(M, "<span class='warning'>Your [name] start[p_s()] to fall apart!</span>")
else
to_chat(M, "<span class='warning'>[src] start[p_s()] to fall apart!</span>")
//This mostly exists so subtypes can call appriopriate update icon calls on the wearer.
/obj/item/clothing/proc/update_clothes_damaged_state(damaged_state = CLOTHING_DAMAGED)
damaged_clothes = damaged_state
/obj/item/clothing/update_overlays()
. = ..()
if(damaged_clothes)
var/index = "[REF(initial(icon))]-[initial(icon_state)]"
var/static/list/damaged_clothes_icons = list()
var/icon/damaged_clothes_icon = damaged_clothes_icons[index]
if(!damaged_clothes_icon)
damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object
damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant
damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon)
damaged_clothes_icons[index] = damaged_clothes_icon
. += damaged_clothes_icon
/*
SEE_SELF // can see self, no matter what
SEE_MOBS // can see all mobs, no matter what
SEE_OBJS // can see all objs, no matter what
SEE_TURFS // can see all turfs (and areas), no matter what
SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are
// in a lit area (via pixel_x,y or smooth movement), can see those pixels
BLIND // can't see anything
*/
/proc/generate_female_clothing(index,t_color,icon,type)
var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color)
var/icon/female_s = icon("icon"='icons/mob/clothing/under/masking_helpers.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]")
female_clothing_icon.Blend(female_s, ICON_MULTIPLY)
female_clothing_icon = fcopy_rsc(female_clothing_icon)
GLOB.female_clothing_icons[index] = female_clothing_icon
/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc.
if(!can_use(user))
return FALSE
visor_toggling()
to_chat(user, "<span class='notice'>You adjust \the [src] [up ? "up" : "down"].</span>")
if(iscarbon(user))
var/mob/living/carbon/C = user
C.head_update(src, forced = 1)
for(var/X in actions)
var/datum/action/A = X
A.UpdateButtonIcon()
return TRUE
/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags
up = !up
clothing_flags ^= visor_flags
flags_inv ^= visor_flags_inv
flags_cover ^= initial(flags_cover)
icon_state = "[initial(icon_state)][up ? "up" : ""]"
if(visor_vars_to_toggle & VISOR_FLASHPROTECT)
flash_protect ^= initial(flash_protect)
if(visor_vars_to_toggle & VISOR_TINT)
tint ^= initial(tint)
/obj/item/clothing/head/helmet/space/plasmaman/visor_toggling() //handles all the actual toggling of flags
up = !up
clothing_flags ^= visor_flags
flags_inv ^= visor_flags_inv
icon_state = "[initial(icon_state)]"
if(visor_vars_to_toggle & VISOR_FLASHPROTECT)
flash_protect ^= initial(flash_protect)
if(visor_vars_to_toggle & VISOR_TINT)
tint ^= initial(tint)
/obj/item/clothing/proc/can_use(mob/user)
if(user && ismob(user))
if(!user.incapacitated())
return 1
return 0
/obj/item/clothing/obj_destruction(damage_flag)
if(damage_flag == "bomb")
var/turf/T = get_turf(src)
//so the shred survives potential turf change from the explosion.
addtimer(CALLBACK_NEW(/obj/effect/decal/cleanable/shreds, list(T, name)), 1)
deconstruct(FALSE)
else if(!(damage_flag in list("acid", "fire")))
body_parts_covered = NONE
slot_flags = NONE
update_clothes_damaged_state(CLOTHING_SHREDDED)
if(isliving(loc))
var/mob/living/M = loc
if(src in M.get_equipped_items(FALSE)) //make sure they were wearing it and not attacking the item in their hands / eating it if they were a moth.
M.visible_message("<span class='danger'>[M]'s [src.name] fall[p_s()] off, [p_theyre()] completely shredded!</span>", "<span class='warning'><b>Your [src.name] fall[p_s()] off, [p_theyre()] completely shredded!</b></span>", vision_distance = COMBAT_MESSAGE_RANGE)
M.dropItemToGround(src)
else
M.visible_message("<span class='danger'>[src] fall[p_s()] apart, completely shredded!</span>", vision_distance = COMBAT_MESSAGE_RANGE)
name = "shredded [initial(name)]" // change the name -after- the message, not before.
else
..()
/// If we're a clothing with at least 1 shredded/disabled zone, give the wearer a periodic heads up letting them know their clothes are damaged
/obj/item/clothing/proc/bristle(mob/living/L)
if(!istype(L))
return
if(prob(0.2))
to_chat(L, "<span class='warning'>The damaged threads on your [src.name] chafe!</span>")