|
| 1 | +//Originally coded by ISaidNo, later modified by Kelenius. Ported from Baystation12. |
| 2 | + |
| 3 | +/obj/structure/closet/crate/secure/loot |
| 4 | + name = "abandoned crate" |
| 5 | + desc = "What could be inside?" |
| 6 | + icon_state = "securecrate" |
| 7 | + base_icon_state = "securecrate" |
| 8 | + integrity_failure = 0 //no breaking open the crate |
| 9 | + var/code = null |
| 10 | + /// Associated list of previous attempts w/ bulls & cows |
| 11 | + var/list/previous_attempts = list() |
| 12 | + var/attempts = 10 |
| 13 | + var/code_length = 4 |
| 14 | + var/qdel_on_open = FALSE |
| 15 | + var/spawned_loot = FALSE |
| 16 | + tamperproof = 90 |
| 17 | + interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND |
| 18 | + |
| 19 | + divable = FALSE // Stop people from "diving into" the crate accidentally, and then detonating it. |
| 20 | + |
| 21 | + /// Possible loot from the crate, selected according to a weighted list: [typepath] = probability % |
| 22 | + var/static/list/possible_loot = list( |
| 23 | + list( // Toys & Entertainment |
| 24 | + /obj/item/melee/skateboard/pro = 5, |
| 25 | + /obj/effect/spawner/abandoned_crate/snappop = 5, |
| 26 | + /obj/effect/spawner/abandoned_crate/posters = 5, |
| 27 | + /obj/item/toy/balloon/corgi = 2, |
| 28 | + /obj/effect/spawner/abandoned_crate/mecha = 2, |
| 29 | + /obj/item/toy/balloon/syndicate = 2, |
| 30 | + /obj/item/toy/plush/snakeplushie = 2, |
| 31 | + /obj/item/bikehorn/airhorn = 2, |
| 32 | + /obj/item/toy/plush/beeplushie = 2, |
| 33 | + /obj/item/toy/plush/lizard_plushie = 2, |
| 34 | + /obj/item/toy/plush/nukeplushie = 1, |
| 35 | + /obj/effect/spawner/abandoned_crate/bwoink = 1, |
| 36 | + /obj/effect/spawner/abandoned_crate/pay_day = 1, |
| 37 | + /obj/item/melee/skateboard/hoverboard = 1, |
| 38 | + /obj/item/implanter/sad_trombone = 1, |
| 39 | + ) = 34, // sum of weights inside list |
| 40 | + |
| 41 | + list( // Weapons & Combat |
| 42 | + /obj/item/melee/baton = 2, |
| 43 | + /obj/item/clothing/gloves/boxing/evil = 1, |
| 44 | + /obj/item/melee/energy/sword/bananium = 1, |
| 45 | + /obj/item/katana = 1, |
| 46 | + ) = 5, |
| 47 | + |
| 48 | + list( // Clothing |
| 49 | + /obj/item/storage/box/syndie_kit/chameleon/broken = 5, |
| 50 | + /obj/item/clothing/shoes/kindle_kicks = 2, |
| 51 | + /obj/effect/spawner/abandoned_crate/kitty = 2, |
| 52 | + /obj/effect/spawner/abandoned_crate/space_suit = 2, |
| 53 | + /obj/effect/spawner/abandoned_crate/fursuit = 2, |
| 54 | + ) = 13, |
| 55 | + |
| 56 | + list( // Tools & Equipment |
| 57 | + /obj/item/modular_computer/pda/clear = 5, |
| 58 | + /obj/item/defibrillator/compact = 2, |
| 59 | + ) = 7, |
| 60 | + |
| 61 | + list( // Materials |
| 62 | + /obj/effect/spawner/abandoned_crate/diamonds = 5, |
| 63 | + /obj/effect/spawner/abandoned_crate/bluespace_crystal = 2, |
| 64 | + /obj/effect/spawner/abandoned_crate/bananium = 2, |
| 65 | + ) = 9, |
| 66 | + |
| 67 | + list( // Seed & Plants |
| 68 | + /obj/item/seeds/firelemon = 5, |
| 69 | + /obj/effect/spawner/abandoned_crate/weed = 1, |
| 70 | + ) = 6, |
| 71 | + |
| 72 | + list( // Consumables |
| 73 | + /obj/effect/spawner/abandoned_crate/booze = 5, |
| 74 | + /obj/effect/spawner/abandoned_crate/boda = 5, |
| 75 | + /obj/item/reagent_containers/cup/glass/bottle/lizardwine = 1, |
| 76 | + ) = 11, |
| 77 | + |
| 78 | + list( // Mobs |
| 79 | + /mob/living/simple_animal/bot/secbot/honkbot = 5, |
| 80 | + /mob/living/simple_animal/pet/gondola = 2, |
| 81 | + /obj/effect/spawner/abandoned_crate/bloodroaches = 1, |
| 82 | + ) = 8, |
| 83 | + |
| 84 | + list( // Medical & Science |
| 85 | + /obj/item/dnainjector/xraymut = 1, |
| 86 | + /obj/item/dnainjector/wackymut = 1, |
| 87 | + ) = 2, |
| 88 | + |
| 89 | + list( // Dangerous |
| 90 | + /obj/effect/spawner/abandoned_crate/gibtonite = 2, |
| 91 | + /obj/effect/spawner/abandoned_crate/mimic = 1, |
| 92 | + /obj/effect/spawner/abandoned_crate/bees = 1, |
| 93 | + ) = 4, |
| 94 | + |
| 95 | + list( // Misc |
| 96 | + /obj/effect/spawner/random/structure/closet_empty/crate = 1, //crate with crate |
| 97 | + ) = 1, |
| 98 | + ) |
| 99 | + |
| 100 | +/obj/structure/closet/crate/secure/loot/Initialize(mapload) |
| 101 | + . = ..() |
| 102 | + code = generate_code(code_length) |
| 103 | + |
| 104 | +/// Generates a random code of specified length with no repeating digits |
| 105 | +/obj/structure/closet/crate/secure/loot/proc/generate_code(length) |
| 106 | + var/list/digits = list("1", "2", "3", "4", "5", "6", "7", "8", "9", "0") |
| 107 | + var/list/code_digits = list() |
| 108 | + |
| 109 | + for(var/i in 1 to length) |
| 110 | + if(!digits.len) |
| 111 | + break |
| 112 | + var/digit = pick(digits) |
| 113 | + code_digits += digit |
| 114 | + digits -= digit //there are never matching digits in the answer |
| 115 | + |
| 116 | + return code_digits.Join("") |
| 117 | + |
| 118 | +//ATTACK HAND IGNORING PARENT RETURN VALUE |
| 119 | +/obj/structure/closet/crate/secure/loot/attack_hand(mob/user, list/modifiers) |
| 120 | + if(!locked) |
| 121 | + return ..() |
| 122 | + if(!user.can_perform_action(src)) |
| 123 | + return |
| 124 | + |
| 125 | + var/input = tgui_input_text(user, title = "Deca-code lock", message = "Enter [code_length] digits. All digits must be unique.", max_length = code_length) |
| 126 | + |
| 127 | + if(input == code) |
| 128 | + if(!spawned_loot) |
| 129 | + spawn_loot() |
| 130 | + tamperproof = 0 // set explosion chance to zero, so we dont accidently hit it with a multitool and instantly die |
| 131 | + SStgui.close_user_uis(user, src) |
| 132 | + togglelock(user) |
| 133 | + return |
| 134 | + |
| 135 | + if(!validate_input(input)) |
| 136 | + to_chat(user, span_notice("You leave the crate alone.")) |
| 137 | + return |
| 138 | + |
| 139 | + to_chat(user, span_warning("A red light flashes.")) |
| 140 | + previous_attempts += list(bulls_and_cows(input)) |
| 141 | + attempts-- |
| 142 | + |
| 143 | + if(attempts <= 0) |
| 144 | + boom(user) |
| 145 | + |
| 146 | +/// Checks if user input is a valid code attempt |
| 147 | +/obj/structure/closet/crate/secure/loot/proc/validate_input(input) |
| 148 | + if(!input || code_length != length(input)) |
| 149 | + return FALSE |
| 150 | + |
| 151 | + var/list/used_digits = list() |
| 152 | + for(var/i = 1 to length(input)) |
| 153 | + var/char = input[i] |
| 154 | + if(!(char >= "0" && char <= "9")) //if a non-digit is found, reject the input |
| 155 | + return FALSE |
| 156 | + if(char in used_digits) //if a digit is repeated, reject the input |
| 157 | + return FALSE |
| 158 | + used_digits += char |
| 159 | + |
| 160 | + return TRUE |
| 161 | + |
| 162 | +/obj/structure/closet/crate/secure/loot/click_alt(mob/living/user) |
| 163 | + attack_hand(user) //this helps you not blow up so easily by overriding unlocking which results in an immediate boom. |
| 164 | + return CLICK_ACTION_SUCCESS |
| 165 | + |
| 166 | +/obj/structure/closet/crate/secure/loot/ui_interact(mob/user, datum/tgui/ui) |
| 167 | + . = ..() |
| 168 | + |
| 169 | + // Attempt to update tgui ui, open and update if needed. |
| 170 | + ui = SStgui.try_update_ui(user, src, ui) |
| 171 | + if(!ui) |
| 172 | + ui = new(user, src, "AbandonedCrate", name) |
| 173 | + ui.open() |
| 174 | + |
| 175 | +/obj/structure/closet/crate/secure/loot/ui_data(mob/user) |
| 176 | + var/list/data = list() |
| 177 | + |
| 178 | + data["previous_attempts"] = previous_attempts |
| 179 | + data["attempts_left"] = attempts |
| 180 | + |
| 181 | + return data |
| 182 | + |
| 183 | +/obj/structure/closet/crate/secure/loot/multitool_act(mob/living/user, obj/item/tool) |
| 184 | + if(!locked) |
| 185 | + return |
| 186 | + if(Adjacent(user)) |
| 187 | + ui_interact(user) |
| 188 | + |
| 189 | + return ITEM_INTERACT_SUCCESS |
| 190 | + |
| 191 | +/// Implements bulls and cows algorithm to compare guess against actual code |
| 192 | +/obj/structure/closet/crate/secure/loot/proc/bulls_and_cows(guess) |
| 193 | + var/bulls = 0 |
| 194 | + var/cows = 0 |
| 195 | + |
| 196 | + for(var/i = 1 to code_length) |
| 197 | + var/guess_char = guess[i] |
| 198 | + var/code_char = code[i] |
| 199 | + |
| 200 | + if(guess_char == code_char) |
| 201 | + bulls++ |
| 202 | + else if(findtext(code, guess_char)) |
| 203 | + cows++ |
| 204 | + |
| 205 | + return list("attempt" = guess, "bulls" = bulls, "cows" = cows) |
| 206 | + |
| 207 | +/obj/structure/closet/crate/secure/loot/emag_act(mob/user, obj/item/card/emag/emag_card) |
| 208 | + . = ..() |
| 209 | + |
| 210 | + if(locked) |
| 211 | + boom(user) // no feedback since it just explodes, thats its own feedback |
| 212 | + return TRUE |
| 213 | + return |
| 214 | + |
| 215 | +/obj/structure/closet/crate/secure/loot/togglelock(mob/user, silent = FALSE) |
| 216 | + if(!locked) |
| 217 | + . = ..() //Run the normal code. |
| 218 | + if(locked) //Double check if the crate actually locked itself when the normal code ran. |
| 219 | + //reset the anti-tampering, number of attempts and last attempt when the lock is re-enabled. |
| 220 | + tamperproof = initial(tamperproof) |
| 221 | + attempts = initial(attempts) |
| 222 | + previous_attempts = list() |
| 223 | + return |
| 224 | + if(tamperproof) |
| 225 | + return |
| 226 | + return ..() |
| 227 | + |
| 228 | +/obj/structure/closet/crate/secure/loot/atom_deconstruct(disassembled = TRUE) |
| 229 | + if(locked) |
| 230 | + boom() |
| 231 | + return |
| 232 | + return ..() |
| 233 | + |
| 234 | +/obj/structure/closet/crate/secure/loot/after_open(mob/living/user, force) |
| 235 | + . = ..() |
| 236 | + if(qdel_on_open) |
| 237 | + qdel(src) |
| 238 | + |
| 239 | +/obj/structure/closet/crate/secure/loot/proc/spawn_loot() |
| 240 | + var/loot = pick_weight_recursive(possible_loot) |
| 241 | + new loot(src) |
| 242 | + spawned_loot = TRUE |
0 commit comments