mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-27 17:41:50 +00:00
* Fixes incorrect operator usage in mecha code (#82570) ## About The Pull Request I completely screwed up and told the original PR author of #82415 (9922d2f237) to use the `XOR` operator instead of the `OR` operator (I wasn't thinking right for some reason when I was reading the ref), anyways this PR just fixes that because I misled the contributor into doing something that wasn't correct and actually would BREAK functionality instead. * Fixes TGUI debugging tools (#82569) This project doesn't interfere with the game logic and aims to fix multiple debugging features that are currently broken. Unfortunately, kitchen sink and debug layout became broken after migration to Redux. This PR aims to fix those features. * Removes unused code for HTML UIs (#82589) ## About The Pull Request This is the final PR for https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA that I've been slowly inching towards the past few months. This removes ``updateDialog``, ``updateUsrDialog``, ``IN_USE``, ``INTERACT_MACHINE_SET_MACHINE``, and everything surrounding it. Also fixes advanced camera consoles not booting you off when you're moved out of reach. We called ``check_eye`` on mob life whenever they had their machine var set, but their machine var would never be set to anything that actually used it, which I found to be a little funny but was also probably my fault. ## Why It's Good For The Game This is poor and unmaintained code used for HTML UIs that we no longer need thanks to TGUI, we should get rid of it to encourage the use of TGUI in the future instead. ## Changelog 🆑 fix: Advanced camera consoles now boots you off when you're moved out of reach. /🆑 * Fixes a variety of input stalling exploits (#82577) ## About The Pull Request Fixes the following input stalling exploits (maybe missed some): - Changing GPS tag - Setting teleporter destination - Request Console Reply - Various AI law board interactions - Note, I used `is_holding` but technically this means these fail with telekinesis. I can swap them to `can_perform_action(...)`, which allows TK, but I noticed some places explicitly deny TK interactions with Ai law boards. Not sure which is preferred. - Borg Rename Board - Plumbing Machines and Ducts - APCs and SMES terminal placements - Stargazers Telepathy - Go Go Gadget Hat ## Changelog 🆑 Melbert fix: You can't change the GPS tag of something unless you can actually use the GPS fix: You can't set the teleporter to a location unless you can actually use the teleporter fix: You can't reply to request console requests unless you can actually use the console fix: You can't update AI lawboards unless you're actually holding them fix: You can't update a borg rename board unless you're actually holding it fix: You can't mess with plumbing machines unless you can actually use them fix: You can't recolor / relayer ducts unless you're actually holding them fix: You can't magically wire APCs and SMESs unless you're right by them fix: You can't use Stargazer Telepathy on people who you can't see fix: You can't configure the Inspector Hat unless you can actually use it /🆑 * [NO GBP] Power outage operation fixes for chem master (#82591) ## About The Pull Request - If the chem master runs out of power mid printing, it will properly stop the printing process and its animation - When transferring reagents it correctly checks if we have enough power without forcing it ## Changelog 🆑 fix: chem master properly shuts down if it loses power mid printing and won't transfer reagents for the same /🆑 * Refactor renaming UNIQUE_RENAME items from the pen to an element (#82491) ## About The Pull Request So a bit ago someone in code_general wanted to make plushies renamable, but learnt that just adding the `UNIQUE_RENAME` flag wouldn't work as pens would murder the plushie and only THEN let you rename it. I noted refactoring both pens and plushies to use the new `item_interaction(...)` procs would Just Solve This, but, well, they didn't really have any coding experience. But, hey, renaming being hardcoded to the pens has annoyed me ever since I laid my eyes upon the hot mess that is paperwork code. So here we are! ### We're making it an element. There's not really much to this, this is mostly the same code but moved to an element and with some minor cleanups. First, we move it all from `/obj/item/pen` to a new element we called `/datum/element/tool_renaming`. With this, instead of having it proc on `/obj/item/pen/afterattack(...)`, we register it to proc on the `COMSIG_ITEM_INTERACTING_WITH_ATOM` signal.6e36ed9840/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm (L59-L62)Secondly, we realize the code is just going through each if statement regardless of whether the previous was correct.6e36ed9840/code/modules/paperwork/pen.dm (L225-L258)And, as we're dealing with text, just make it a switch statement instead. ```dm switch(pen_choice) if("Rename") (...) if("Description") (...) if("Reset") (...) ``` Then, we replace all single letter variables with descriptive ones, replace the if-elses with early returns, and make it actually return item interaction flags. Finally, we slap this onto the pen, and we're done. Now we can slap it onto other fitting renaming tools, and it uses the proper item interaction system. ## Why It's Good For The Game I feel it's generally better to not hardcode this to just pens, we have plenty other writing utensils and possible renaming tools. It's also a bit cleaner than before. Apart from that, moves it from using `afterattack(...)` to the proper item interaction chain by using `COMSIG_ITEM_INTERACTING_WITH_ATOM`, which should reduce janky interactions. ## Changelog 🆑 refactor: Instead of being hardcoded to the pen, renaming items is now an element. Currently only pens have this, and functionality should be the same, but please report it if you find any items that were renamable but now aren't. /🆑 * Adds various quality of life changes for cooking to make it less click intensive. (#82566) ## About The Pull Request - Increases tray item size by 1 item. - Ranges and griddles can now be fed from trays. Click when closed => fill soup pot. Click when open => fill associated oven tray. Right click when open => fill tray from oven tray Click griddle => fill griddle surface. Right click => fill tray from griddle surface - Martian batter is now 5u of each ingredient into 10u of batter. Hopefully will make it bug out less where it makes far fewer reagents than it is supposed to, fixing reagents, or well soups specifically... is out of scope for this PR. - Adds the ability to print soup pots and large trays from the service lathe Soup pot: 5 Iron sheets, 0.4 bluespace crystal (given their size of 200U) Large serving tray: 2 iron sheets ## Why It's Good For The Game Makes cooking a lot less tedious. Especially for people with low precision when it comes to filling oven trays. This also bring the behavior up to parity with how you can click microwaves with trays to fill them, ditto for the food processor. It also allows chef to use the whole capacity of an oven, as previously you couldn't easily click 6 cake batters or other giant sprites onto the tiny tray. The tray is now sized to be able to easily feed a griddle 8 items. ## Changelog 🆑 qol: chef equipment can now deposit and withdraw to/from trays! qol: chef now has access to griddle and oven sized trays! qol: service can now print soup pots /🆑 --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> * Removes grid usage + heavy refactors (#82571) ## About The Pull Request Grid has been deprecated for quite some time and we still use it. I won't completely remove the component, this way downstreams won't immediately suffer, but I can remove it from usage. Some of these UIs had issues with them and as a hobby project I've refactored them into typescript / rebuilt them. Airlock electronics, for instance, looks substantially better. <details> <summary>before/after as requested</summary> current airlock electronics scrolls into oblivion  updated  </details> ## Why It's Good For The Game Code improvement + probably UI bug fixes ## Changelog 🆑 fix: Airlock electronics and other access-config type UIs should look much better. /🆑 * modular fixes * [No GBP] Removes cogbar from some stealthy actions (#82593) Issue brought some missed hidden actions to my attention. I left cogbars in for _breaking_ handcuffs because resisting is sort of a gray area. On one hand, you don't want someone to see you doing it; on the other, there is a visible warning that you started doing it. So, meet in the the middle, breaking handcuffs is still visible while resisting isn't. Closes #82583 Cogbars are not intended to ruin stealth 🆑 fix: Deviants buffed: Rogue shoelacing, pickpocketing and restraint resisting no longer give cogbar icons. /🆑 * [NO GBP] ...Remember to add SIGNAL_HANDLER (#82630) ## About The Pull Request Just realized I forgot to add `SIGNAL_HANDLER` to the all-nighter `on_removed_limb(...)` proc, even though it handles signals. ## Why It's Good For The Gamefe26373572/code/__DEFINES/dcs/helpers.dm (L9-L11)* React cleanup (#82607) ## About The Pull Request - No defaultHooks in react. Might fix issues where pages were not scrollable on hover. - createRef in a functional component. should be useref ## Why It's Good For The Game Code improvement * Security photobooths have their own ID (#82628) ## About The Pull Request Prevents the HoP's photobooth button from connecting to the security photobooth via having the same ID. ## Why It's Good For The Game I forgot to add this when I made the security photobooth but it's important that by default without any varedits, the HoP and security photobooths stay separate. ## Changelog 🆑 fix: The HoP's photobooth button is now consistently connected to the HoP's photobooth. /🆑 * Fix buckled alert unbuckling not working properly (#82627) ## About The Pull Request So funny thing, while trying to reproduce a different issue on the current master, I coincidentally let my local instance start without reading, latejoined on the shuttle, and I noticed it wasn't letting me unbuckle as easily. Looking into this a bit later, it seems as if it's a line #82593 accidentally changed while moving around the `/mob/living/carbon/resist_buckle()` proc's flow.fe26373572/code/modules/mob/living/carbon/carbon.dm (L238-L241)While before it was ```dm /mob/living/carbon/resist_buckle() if(HAS_TRAIT(src, TRAIT_RESTRAINED)) (...) else buckled.user_unbuckle_mob(src,src) ``` Just changing this to `buckled.user_unbuckle_mob(src, src)` fixes this. ## Why It's Good For The Game Fixes buckled alert unbuckling not working properly. Fixes #82627. ## Changelog 🆑 fix: Clicking the buckled alert unbuckles you again. /🆑 * Advanced camera consoles correctly deactivates when something happens to it or the user (#82619) ## About The Pull Request - Fixes #82520 1. The eye deactivates when the machine is destroyed/deleted 2. The eye deactivates when the machine loses power 3. The computer constantly moniters the users status inside `process()` and will deactivate when anything happens to them. Its not enough to just hook onto to the mobs `COMSIG_MOVABLE_MOVED` signal. Literarly anything can happen to them so we have to check constantly for any changes ## Changelog 🆑 fix: advanced camera consoles correctly deactivate when something happens(no proximity, no power etc) to its user /🆑 * Oven tray checks for ovens (#82615) ## About The Pull Request - Fixes #82610 Only oven trays have this proc not serving trays or other stuff  Also oven trays have a null atom storage which prevents it from being put back in the oven after taking it out. So we remove that check ## Changelog 🆑 fix: you can put back the oven tray after you take it out fix: only oven trays are allowed in ovens preventing baked food runtimes /🆑 * Living Limb fixes (feat: Basic mobs attack random body zones again) (#82556) ## About The Pull Request Reworks Living Limb code to fix a bunch of runtimes and issues I saw while testing Bioscrambler. Specifically, the contained mobs are now initialised via element following attachment so that signal registration can occur at the correct time. This allows limbs to function correctly when added from nullspace via admin panel or bioscrambler. Secondarily (and more wide-ranging) at some point (probably #79563) we inadvertently made basic mobs only attack the target's chest instead of spreading damage. This is problematic for Living Flesh which can only attach itself to damaged limbs but was left unable to attack damaged limbs. I've fixed this in a way which is maybe stupid: adding an element which randomises attack zone pre-attack. Living limbs also limit this to _only_ limbs (although it will fall back to chest if you have no limbs at all). This is _technically_ still different, the previous behaviour used `adjustBruteLoss` and `adjustFireLoss` and would spread the damage across your entire body, but there isn't a route to that via the new interface and this seems close enough. ## Changelog 🆑 fix: Living Limbs created by Bioscrambler will be alive. fix: Living Limbs can once more attach themselves to your body. balance: Living Limbs will prioritise attacking your limbs. fix: Basic Mobs will once again spread their damage across body zones instead of only attacking your chest. /🆑 * RPG Loot: Revisited & READY (#82533) Revival of #72881 A new alt click window with a tarkov-y loading spinner. Replaces the object item window in stat panel. <details> <summary>vids</summary> toggleable grouping:  now lists the floor as first obj:  in action:  </details> - search by name - 515 image generator is much faster than alt click menu - opening a gargantuan amount of items shouldnt freeze your screen - groups similar items together in stacks by default, toggleable - shows tile as first item - <kbd>Shift</kbd> and <kbd>Ctrl</kbd> compatible with LMB 🖱️ - RMB points points at items (sry i could not get MMB working) - key <kbd>Esc</kbd> to exit the window. For devs: - A new image generation tech. - An error refetch mechanic to the Image component - It does not "smart track" the items being added to the pile, just reopen or refresh. This was a design decision. Honestly I just dislike the stat panel Fixes #53824 Fixes  🆑 add: Added a loot window for alt-clicking tiles. del: Removed the item browser from the stat panel. /🆑 --------- Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Co-authored-by: AnturK <AnturK@users.noreply.github.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> * Reverts parts of #82602 (nodeath checks) (#82637) ## About The Pull Request Reverts the nodeath checks of #82602 I opened a review thinking these checks were sus and the PR author said they would remove them, but it was merged before that happened. TL;DR 1. I just noticed this now but it only affects carbons / humans it doesn't even cover living or any other subtypes 2. Kinda sus. Some code intentionally skips checking nodeath (I guess? Like removing the brain for example) so we would need a larger audit of this rather than haphazardly throwing it in. * Fixes to battle arcade (#82620) ## About The Pull Request Added gear for world nine, removed the "Gear" gear that did nothing. Made counterattacks to kill an enemy properly kill the enemy. I renamed some gear items to fit the theme of the area they are unlocked in just as a small thing. ## Why It's Good For The Game Closes https://github.com/tgstation/tgstation/issues/82613 ## Changelog 🆑 fix: Battle arcade's higher levels no longer gives you a "Gear" gear, and counterattacks can now properly kill enemies. /🆑 * Fixes SMES terminal placing under the SMES and not under the player (#82665) ## About The Pull Request Changes `src` to`user` to get intended behavior. * Birdshot: Toy crate (#82633) ## About The Pull Request Gives the clown+mime their toy crate. ## Why It's Good For The Game *honk* * tram ai sat starts with a full smes (#82646) ## About The Pull Request consistency and also this is fixes a bug introduced by that one power refactor ## Why It's Good For The Game bug bad ## Changelog 🆑 fix: tramstation AI sat starts full /🆑 * [no gbp] Space Ruin bioscramblers shouldn't chase people around (#82649) ## About The Pull Request See title They wouldn't lock on to people on the station from a space ruin, but would to whoever entered their z level the second it was entered. Also fixes bug where I changed `status_flags` to `status_effects` for some reason which isn't where you look for godmode ## Why It's Good For The Game We have a space ruin whcih several (coreless) anomalies spawn on, the bioscrambler was put as an option because it was already immortal. It's weird though to zone into the ruin and immediately have every anomaly in there lock onto you, the best intended effect is probably for these ones specifically not to be bloodthirsty. We kind of only care about that behaviour on the station. ## Changelog 🆑 fix: Anomalous Research ruin Bioscrambler anomalies won't home in on targets fix: Bioscrambler won't randomly drop its target for no reason /🆑 * Sunders the many unused sprites and organizes what's left in structures.dmi (#82658) ## About The Pull Request Hello again, I noticed the /obj/structures.dmi file had a lot of unused stuff like tables from two generations ago, so I changed some stuff around: - Many unused, old icons deleted, mostly window variants used in old smoothing systems I imagine - Reorganized many sprites in the file so they're more grouped together - Tweaked some barricade sprite naming to be consistent/standardized, and to let others know they're not _too_ old... - Fixed a misnomer that I believe was making directional tinted windows look like frosted windows ## Why It's Good For The Game Saves on file space, and satisfies your brain's pattern recognition bits ### Spriting Old:  New:  also good lord those linen bin sprites are a crime ## Changelog 🆑 fix: Probably fixed directional tinted windows looking like directional frosted windows image: Deleted a bunch of unused structure sprites /🆑 * Birdshot Wall Sanity Pass (#82598) ## About The Pull Request Cleans up minor artifacting in the Birdshot Sec-Tram Closed Turfs ## Why It's Good For The Game Someone definitely didn't mean to place some machines under Closed Turfs. This barely qualifies as player facing. ## Changelog 🆑 fix: Cleans up some rocks on Birdshot /🆑 * [NO GBP] Fixes deconstruction of closets & crates under a special case (#82612) ## About The Pull Request So if a closet/crate has the `NO_DEBRIS_AFTER_DECONSTRUCTION` set on it and if someone/something is still inside, then after deconstruction they get deleted rather than getting dumped out first. Could cause potential hard delete of mobs & stuff. We don't want to deal with that ## Changelog 🆑 fix: closets & crates will dump all contents out first before deleting itself regardless of `NO_DEBRIS_AFTER_DECONSTRUCTION` thus not for e.g. hard deleting mobs inside it /🆑 * Fixes ordinance lab igniter in IceBox (#82595) ## About The Pull Request - Fixes #82294 Basically the same idea of merging ordanance lab with the burn chamber so they share the same apc as already implemented in #82322 ## Changelog 🆑 fix: Ordinance lab igniter in Icebox works again /🆑 * Birdshot: engi wardrope. (#82639) ## About The Pull Request Add engi wardrope on Birdshot. ## Why It's Good For The Game Birdshot doesn't have engi wardrope. 🆑 fix: Birdshot now have engi wardrope /🆑 * Gives shadow walk a new, spookier, and shorter sound effect that no longer ignores walls (#82689) ## About The Pull Request This gives shadow walk a snazzy new sound effect for entering/exiting jaunt. https://github.com/tgstation/tgstation/assets/28870487/c25f720f-5bad-4063-8d6e-140fd41bd740 This also has the sounds it plays no longer passes through walls. ## Why It's Good For The Game The ethereal_entrance/exit sound effects are drawn out, and pretty grating. They work for the other jaunts they're used for because a jaunt typically lasts longer than the sound itself. Nightmares are frequently dancing in and out of jaunt, and the sound effects for entering/exiting tend to overlap. It gets loud and annoying really fast. This sound effect is quicker, spookier, and more distinct. As for making the sound not ignore walls, I think it's pretty dumb how easy it is to detect the spooky scary shadow antag just by sitting in your department. It takes a lot of the initial fear and paranoia they have the potential for is wasted when Joe Geneticist can hear them messing around in their territory without having to leave their chair. ## Changelog 🆑 Rhials sound: Nightmare has a new sound effect for entering/exiting shadow jaunt. It also no longer can be heard through walls. /🆑 * [MIRROR] Alt click refactor (#2029) * Alt click refactor * Some early conflict removal * Big modular refactor * Update console.dm * Update paper.dm --------- Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Co-authored-by: Mal <13398309+vinylspiders@users.noreply.github.com> * Yeets `ATTACK_QDELETED`, fixes welding torches not using fuel on attacking non-mobs (2 year old bug) (#82694) ## About The Pull Request - Deletes `ATTACK_QDELETED` - May have been necessary in the past but it's pointless now. All it does is clutter the attack chain. Perish. - Fixes welders not using fuel on attacking non-mobs - #65762 "fixed" welders consuming fuel on clicking turfs by adding an `isliving` check and not an `ismovable` check? ## Changelog 🆑 Melbert fix: Blobs may rejoice, welding torches now consume fuel when attacking objects again after two years. /🆑 * electric_welder fire * Quirks, which give items, now have quirk_item arg specified as obj/item, instead of being just a var (#82650) ## About The Pull Request quirk_item is now /obj/item, since it will allow for calling procs or getting variables from this item It's required for non-modular translation to call for item's name to remove articles ## Why It's Good For The Game It's always an item, and if it's a path, it's already checked for it. Better usage in the future. * turns martial arts gloves into a component (#82599) sleeping carp gloves also work on mind init this means for the sake of deathmatch you dont have to put them off and on fixes #82321 🆑 fix: you no longer need to put your sleeping carp gloves off and on in Deathmatch to get the martial art /🆑 --------- Co-authored-by: san7890 <the@san7890.com> * Regal Rats can now tear down posters (#82673) ## About The Pull Request i was fixing something on bagil and someone who was playing a regal rat (after the round ended) said they wanted to be able to tear down posters as a regal rat so i decided to code it because it made sense. it's an element so literally any mob can tear down posters but i can't think of any other mobs that would make sense to let it tear down posters so we'll leave it just for _The Champion of All Mislaid Creatures_ for now ## Why It's Good For The Game Regal Rats should be all about sludgemaxxing and fucking up maintenance to make it look even more grody than it should be. Being able to tear up those disgusting and well-drawn posters to leave behind nothing but scraps fits that motif. The element has a `do_after()` just to make sure His Holiness doesn't accidentally tear down his posters while clicking (i think all mobs should have this but that's a different issue man) also includes some code improvement and user feedback in some failure cases that already existed in the code. ## Changelog 🆑 add: Regal Rats are now able to tear down those colorful posters those weird grey creatures keep spackling up on the walls of their rightful domain. /🆑 * Adds "Strong Stomach" quirk, a core CDDA/PZ quirk we've sorely been missing. Also Deviant Tastes dirty food re-nerf. (#82562) ## About The Pull Request - Adds Strong Stomach quirk. - 4 points - You can eat dirty food without risk of getting disease. - You suffer less negative effects from vomiting. Vomit stuns you for half the duration, and you lose half as much nutrition. - Reverts https://github.com/tgstation/tgstation/pull/76864 , integrates its effects into Strong Stomach instead. ## Why It's Good For The Game - Lotta people (namely Lizards and sometimes Felines with Deviant Tastes) run gimmicks involving them being a gremlin person and eating trash off the ground, and it's rather hard to accomplish this now since it makes you a public medbay enemy # 1. This quirk should give them an option to avoid that. - Also (as mentioned in the title) both CDDA and PZ have this trait and I can't believe we're missing it! This is something in modifiable-character-traits/quirks-101. - I moved the effects from #76864 to this quirk because 1. I thought it was more fitting and 2. I thought the original PR was kinda wack for what is (generally) a neutral quirk. ## Changelog 🆑 Melbert add: Adds the Strong Stomach quirk, which allows you to eat grimy food without worry about disease, and makes you a bit more resilient to the effects of vomiting. del: Deviant Tastes no longer prevents you from getting a negative moodlet from eating dirty food. Strong Stomach does that now. /🆑 --------- Co-authored-by: Jacquerel <hnevard@gmail.com> * Remove several functions from collections.js which have ES5 equivalents (#82417) * Makes it EVEN EASIER to work with atom item interactions ft. "Leaf and Branch" & "Death to Chains" (#82625) * apc fix * Gulag Adjustments Two (#82561) ## About The Pull Request I have received feedback that after the prior changes in #81971, the gulag is still a little bit too subject to RNG. The main culprit (as in my previous PR) is Iron being kind of cheap and the fact that unlike the old Gulag you no longer have any way of headhunting more valuable materials (everything appears as boulders on your ore scanner). My solution to this is wider than the last one of tweaking point values, but also much simpler: Just make every boulder you mine be worth the same amount of points regardless of what is inside of it. On the average test I made I could comfortably mine about 40-45 boulders in ten minutes. We'll make some adjustments to that rather than leaving 40 as the target number; Most players upon being teleported to the gulag are going to spend a few minutes whining and bemoaning their fate instead of getting straight to work. I had the benefit of being able to make sure my run started as soon as a storm ended so I wouldn't need any kind of midpoint break. I was also always the only person playing on my local instance, there hadn't been any other pesky prisoners before me who had already mined out all the nearest available deposits. And of course, let us not forget, I am an MLG master league ss13 player who was surely performing well above average. So we'll round that down to: Each boulder is worth 33 points, meaning you need to collect 31 boulders to complete a 1000 point (roughly ten minute) sentence. How do I ensure that every boulder is worth the same amount of points? Well it's pretty easy. One boulder = one material sheet. One material sheet = 33 points. Simple. "Now Jacquerel", I hear you not saying because you don't want me to know about this thing you would prefer to do instead of hitting rocks outside; "if I simply smash all of the tables and microwaves and botany trays and bed in the gulag I can easily get like 65 sheets of Iron, which is almost enough to buy the freedom for two entire people!" Unfortunately I knew you were going to try and do that and the prisoner point machine will only give you points for material sheets which have been printed from the material smelter (well, any material smelter actually but you should probably use the one in the gulag). You'll be able to tell because if you examine a valid material sheet it will mention a little maker's mark on it, which is absent in the beat-up iron that you get from smashing furniture to bits. Also glass is worth 0 points. Don't waste time digging up that shit. As glass has had all of its point value removed, I have added a "work pit" to the gulag to compensate. You can pull boulders out of this indefinitely via effort, however it also stamcrits you every time. It's not very fun to do this, but that's because I would prefer you to go find the rocks out in the field instead. This is a last resort. You can do this if there's no boulders left to mine or if you really really really hate mining and would rather very slowly click on one tile repeatedly to get your boulders instead. As a tiny bonus doing this gives workout experience. This isn't a totally ideal solution but I think it'll do for now. ## Why It's Good For The Game What we want out of the gulag is: - Something where officers can vaguely approximate an expected sentence duration. - A task that requires players to actually be spending that time doing something to get out of here. - Produces at least some amount of useful materials. In I think roughly that order. I hope this change accomplishes all three of these in a way that is somewhat predictable rather than throwing darts at a board. ## Changelog 🆑 balance: Gulag mining has been rebalanced so that every boulder is worth the same amount of points to mine for a prisoner regardless of what it contains, and should be more consistent. add: A vent which boulders can be hauled out of by hand has been added to the gulag which you can use if there's nothing left to mine. It is very slow, but at least it gives you a workout... /🆑 * stone * Makes test merge bot continue with other PRs if updating one fails. (#82717) Right now updating https://github.com/tgstation/tgstation/pull/81089#issuecomment-1907296233 fails because it exceeds github character limit for comments. This will make it work until backed is updated. * Fixes the RnD console by adding a removed import (#82750) ## About The Pull Request The 'map' import was removed from this file by #82417 but it's still used in place in code. This re-adds the import ## Why It's Good For The Game Fixes RnD consoles ## Changelog 🆑 fix: Fixed RnD consoles not being able to be opened. /🆑 Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> * Fixes cargo import (#82755) ## About The Pull Request One of the imports got removed and there were no warnings... Man if only there were a technology that could warn us in advance ## Why It's Good For The Game UI fixes ## Changelog 🆑 fix: Fixed a bluescreen in cargo console /🆑 * fixes * Fixes, fixes. * Pre-emptive mirror of https://github.com/tgstation/tgstation/pull/82892 * Turf weakref persists in changeturf / Fix plasma cutters (#82906) ## About The Pull Request Turf references don't change so logically, turf weakrefs wouldn't change if the turf changes. By not doing this this can cause bugs: See #82886 . (This Fixes #82886) (Projectiles hold a list of weakrefs to atoms hit to determine what they have already hit. Because turf weakrefs reset, we could "hit" the same turf twice if it destroyed the turf. Old behavior - this was fine but now that they're weakrefs, we get two weakref datums in the list that point to the same ref.) Less hacky alternative to #82901 . (Closes #82901) ## Changelog 🆑 Melbert fix: Plasma cutters work again /🆑 --------- Co-authored-by: san7890 <the@san7890.com> Co-authored-by: Interception&? <137328283+intercepti0n@users.noreply.github.com> Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com> Co-authored-by: Ketrai <zottielolly@gmail.com> Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Co-authored-by: Jacquerel <hnevard@gmail.com> Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Co-authored-by: AnturK <AnturK@users.noreply.github.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: Iajret <8430839+Iajret@users.noreply.github.com> Co-authored-by: vect0r <71346830+Vect0r2@users.noreply.github.com> Co-authored-by: jimmyl <70376633+mc-oofert@users.noreply.github.com> Co-authored-by: AMyriad <143908044+AMyriad@users.noreply.github.com> Co-authored-by: Zytolg <33048583+Zytolg@users.noreply.github.com> Co-authored-by: Xackii <120736708+Xackii@users.noreply.github.com> Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com> Co-authored-by: NovaBot <154629622+NovaBot13@users.noreply.github.com> Co-authored-by: Mal <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: larentoun <31931237+larentoun@users.noreply.github.com> Co-authored-by: Arthri <41360489+Arthri@users.noreply.github.com> Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> Co-authored-by: Useroth <37159550+Useroth@users.noreply.github.com>
1889 lines
71 KiB
Plaintext
1889 lines
71 KiB
Plaintext
/// Fallback time if none of the config entries are set for USE_LOW_LIVING_HOUR_INTERN
|
|
#define INTERN_THRESHOLD_FALLBACK_HOURS 15
|
|
|
|
/// Max time interval between projecting holopays
|
|
#define HOLOPAY_PROJECTION_INTERVAL (7 SECONDS)
|
|
|
|
/* Cards
|
|
* Contains:
|
|
* DATA CARD
|
|
* ID CARD
|
|
* FINGERPRINT CARD HOLDER
|
|
* FINGERPRINT CARD
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
* DATA CARDS - Used for the IC data card reader
|
|
*/
|
|
|
|
/obj/item/card
|
|
name = "card"
|
|
desc = "Does card things."
|
|
icon = 'icons/obj/card.dmi'
|
|
inhand_icon_state = "card-id"
|
|
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi'
|
|
w_class = WEIGHT_CLASS_TINY
|
|
|
|
/// Cached icon that has been built for this card. Intended to be displayed in chat. Cardboards IDs and actual IDs use it.
|
|
var/icon/cached_flat_icon
|
|
|
|
/obj/item/card/suicide_act(mob/living/carbon/user)
|
|
user.visible_message(span_suicide("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
|
|
return BRUTELOSS
|
|
|
|
/obj/item/card/update_overlays()
|
|
. = ..()
|
|
cached_flat_icon = null
|
|
|
|
/// If no cached_flat_icon exists, this proc creates it and crops it. This proc then returns the cached_flat_icon. Intended for use displaying ID card icons in chat.
|
|
/obj/item/card/proc/get_cached_flat_icon()
|
|
if(!cached_flat_icon)
|
|
cached_flat_icon = getFlatIcon(src)
|
|
cached_flat_icon.Crop(ID_ICON_BORDERS)
|
|
return cached_flat_icon
|
|
|
|
/*
|
|
* ID CARDS
|
|
*/
|
|
|
|
/// "Retro" ID card that renders itself as the icon state with no overlays.
|
|
/obj/item/card/id
|
|
name = "retro identification card"
|
|
desc = "A card used to provide ID and determine access across the station."
|
|
icon_state = "card_grey"
|
|
worn_icon_state = "nothing"
|
|
slot_flags = ITEM_SLOT_ID
|
|
armor_type = /datum/armor/card_id
|
|
resistance_flags = FIRE_PROOF | ACID_PROOF
|
|
|
|
/// The name registered on the card (for example: Dr Bryan See)
|
|
var/registered_name = null
|
|
/// Linked bank account.
|
|
var/datum/bank_account/registered_account
|
|
|
|
/// Linked holopay.
|
|
var/obj/structure/holopay/my_store
|
|
/// Cooldown between projecting holopays
|
|
COOLDOWN_DECLARE(last_holopay_projection)
|
|
/// List of logos available for holopay customization - via font awesome 5
|
|
var/static/list/available_logos = list("angry", "ankh", "bacon", "band-aid", "cannabis", "cat", "cocktail", "coins", "comments-dollar",
|
|
"cross", "cut", "dog", "donate", "dna", "fist-raised", "flask", "glass-cheers", "glass-martini-alt", "hamburger", "hand-holding-usd",
|
|
"hat-wizard", "head-side-cough-slash", "heart", "heart-broken", "laugh-beam", "leaf", "money-check-alt", "music", "piggy-bank",
|
|
"pizza-slice", "prescription-bottle-alt", "radiation", "robot", "smile", "skull-crossbones", "smoking", "space-shuttle", "tram",
|
|
"trash", "user-ninja", "utensils", "wrench")
|
|
/// Replaces the "pay whatever" functionality with a set amount when non-zero.
|
|
var/holopay_fee = 0
|
|
/// The holopay icon chosen by the user
|
|
var/holopay_logo = "donate"
|
|
/// Maximum forced fee. It's unlikely for a user to encounter this type of money, much less pay it willingly.
|
|
var/holopay_max_fee = 5000
|
|
/// Minimum forced fee for holopay stations. Registers as "pay what you want."
|
|
var/holopay_min_fee = 0
|
|
/// The holopay name chosen by the user
|
|
var/holopay_name = "holographic pay stand"
|
|
|
|
/// Registered owner's age.
|
|
var/registered_age = 18 //SKYRAT EDIT - ORIGINAL (13)
|
|
|
|
/// The job name registered on the card (for example: Assistant).
|
|
var/assignment
|
|
|
|
/// Trim datum associated with the card. Controls which job icon is displayed on the card and which accesses do not require wildcards.
|
|
var/datum/id_trim/trim
|
|
|
|
/// Access levels held by this card.
|
|
var/list/access = list()
|
|
|
|
/// List of wildcard slot names as keys with lists of wildcard data as values.
|
|
var/list/wildcard_slots = list()
|
|
|
|
/// Boolean value. If TRUE, the [Intern] tag gets prepended to this ID card when the label is updated.
|
|
var/is_intern = FALSE
|
|
|
|
/datum/armor/card_id
|
|
fire = 100
|
|
acid = 100
|
|
|
|
/obj/item/card/id/apply_fantasy_bonuses(bonus)
|
|
. = ..()
|
|
if(bonus >= 15)
|
|
add_access(SSid_access.get_region_access_list(list(REGION_ALL_GLOBAL)), mode = FORCE_ADD_ALL)
|
|
else if(bonus >= 10)
|
|
add_access(SSid_access.get_region_access_list(list(REGION_ALL_STATION)), mode = FORCE_ADD_ALL)
|
|
else if(bonus <= -10)
|
|
clear_access()
|
|
|
|
/obj/item/card/id/Initialize(mapload)
|
|
. = ..()
|
|
|
|
var/datum/bank_account/blank_bank_account = new("Unassigned", SSjob.GetJobType(/datum/job/unassigned), player_account = FALSE)
|
|
registered_account = blank_bank_account
|
|
registered_account.replaceable = TRUE
|
|
|
|
// Applying the trim updates the label and icon, so don't do this twice.
|
|
if(ispath(trim))
|
|
SSid_access.apply_trim_to_card(src, trim)
|
|
else
|
|
update_label()
|
|
update_icon()
|
|
|
|
register_context()
|
|
|
|
RegisterSignal(src, COMSIG_ATOM_UPDATED_ICON, PROC_REF(update_in_wallet))
|
|
if(prob(1))
|
|
ADD_TRAIT(src, TRAIT_TASTEFULLY_THICK_ID_CARD, ROUNDSTART_TRAIT)
|
|
|
|
/obj/item/card/id/Destroy()
|
|
if (registered_account)
|
|
registered_account.bank_cards -= src
|
|
if (my_store)
|
|
QDEL_NULL(my_store)
|
|
return ..()
|
|
|
|
/obj/item/card/id/get_id_examine_strings(mob/user)
|
|
. = ..()
|
|
. += list("[icon2html(get_cached_flat_icon(), user, extra_classes = "bigicon")]")
|
|
|
|
/obj/item/card/id/get_examine_string(mob/user, thats = FALSE)
|
|
return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]"
|
|
|
|
/**
|
|
* Helper proc, checks whether the ID card can hold any given set of wildcards.
|
|
*
|
|
* Returns TRUE if the card can hold the wildcards, FALSE otherwise.
|
|
* Arguments:
|
|
* * wildcard_list - List of accesses to check.
|
|
* * try_wildcard - If not null, will attempt to add wildcards for this wildcard specifically and will return FALSE if the card cannot hold all wildcards in this slot.
|
|
*/
|
|
/obj/item/card/id/proc/can_add_wildcards(list/wildcard_list, try_wildcard = null)
|
|
if(!length(wildcard_list))
|
|
return TRUE
|
|
|
|
var/list/new_wildcard_limits = list()
|
|
|
|
for(var/flag_name in wildcard_slots)
|
|
if(try_wildcard && !(flag_name == try_wildcard))
|
|
continue
|
|
var/list/wildcard_info = wildcard_slots[flag_name]
|
|
new_wildcard_limits[flag_name] = wildcard_info["limit"] - length(wildcard_info["usage"])
|
|
|
|
if(!length(new_wildcard_limits))
|
|
return FALSE
|
|
|
|
var/wildcard_allocated
|
|
for(var/wildcard in wildcard_list)
|
|
var/wildcard_flag = SSid_access.get_access_flag(wildcard)
|
|
wildcard_allocated = FALSE
|
|
for(var/flag_name in new_wildcard_limits)
|
|
var/limit_flags = SSid_access.wildcard_flags_by_wildcard[flag_name]
|
|
if(!(wildcard_flag & limit_flags))
|
|
continue
|
|
// Negative limits mean infinite slots. Positive limits mean limited slots still available. 0 slots means no slots.
|
|
if(new_wildcard_limits[flag_name] == 0)
|
|
continue
|
|
new_wildcard_limits[flag_name]--
|
|
wildcard_allocated = TRUE
|
|
break
|
|
if(!wildcard_allocated)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* Attempts to add the given wildcards to the ID card.
|
|
*
|
|
* Arguments:
|
|
* * wildcard_list - List of accesses to add.
|
|
* * try_wildcard - If not null, will attempt to add all wildcards to this wildcard slot only.
|
|
* * mode - The method to use when adding wildcards. See define for ERROR_ON_FAIL
|
|
*/
|
|
/obj/item/card/id/proc/add_wildcards(list/wildcard_list, try_wildcard = null, mode = ERROR_ON_FAIL)
|
|
var/wildcard_allocated
|
|
// Iterate through each wildcard in our list. Get its access flag. Then iterate over wildcard slots and try to fit it in.
|
|
for(var/wildcard in wildcard_list)
|
|
var/wildcard_flag = SSid_access.get_access_flag(wildcard)
|
|
wildcard_allocated = FALSE
|
|
for(var/flag_name in wildcard_slots)
|
|
if(flag_name == WILDCARD_NAME_FORCED)
|
|
continue
|
|
|
|
if(try_wildcard && !(flag_name == try_wildcard))
|
|
continue
|
|
|
|
var/limit_flags = SSid_access.wildcard_flags_by_wildcard[flag_name]
|
|
|
|
if(!(wildcard_flag & limit_flags))
|
|
continue
|
|
|
|
var/list/wildcard_info = wildcard_slots[flag_name]
|
|
var/wildcard_limit = wildcard_info["limit"]
|
|
var/list/wildcard_usage = wildcard_info["usage"]
|
|
|
|
var/wildcard_count = wildcard_limit - length(wildcard_usage)
|
|
|
|
// Negative limits mean infinite slots. Positive limits mean limited slots still available. 0 slots means no slots.
|
|
if(wildcard_count == 0)
|
|
continue
|
|
|
|
wildcard_usage |= wildcard
|
|
access |= wildcard
|
|
wildcard_allocated = TRUE
|
|
break
|
|
// Fallback for if we couldn't allocate the wildcard for some reason.
|
|
if(!wildcard_allocated)
|
|
if(mode == ERROR_ON_FAIL)
|
|
CRASH("Wildcard ([wildcard]) could not be added to [src].")
|
|
|
|
if(mode == TRY_ADD_ALL)
|
|
continue
|
|
|
|
// If the card has no info for historic forced wildcards, create the list.
|
|
if(!wildcard_slots[WILDCARD_NAME_FORCED])
|
|
wildcard_slots[WILDCARD_NAME_FORCED] = list(limit = 0, usage = list())
|
|
|
|
var/list/wildcard_info = wildcard_slots[WILDCARD_NAME_FORCED]
|
|
var/list/wildcard_usage = wildcard_info["usage"]
|
|
wildcard_usage |= wildcard
|
|
access |= wildcard
|
|
wildcard_info["limit"] = length(wildcard_usage)
|
|
|
|
/**
|
|
* Removes wildcards from the ID card.
|
|
*
|
|
* Arguments:
|
|
* * wildcard_list - List of accesses to remove.
|
|
*/
|
|
/obj/item/card/id/proc/remove_wildcards(list/wildcard_list)
|
|
var/wildcard_removed
|
|
// Iterate through each wildcard in our list. Get its access flag. Then iterate over wildcard slots and try to remove it.
|
|
for(var/wildcard in wildcard_list)
|
|
wildcard_removed = FALSE
|
|
for(var/flag_name in wildcard_slots)
|
|
if(flag_name == WILDCARD_NAME_FORCED)
|
|
continue
|
|
|
|
var/list/wildcard_info = wildcard_slots[flag_name]
|
|
var/wildcard_usage = wildcard_info["usage"]
|
|
|
|
if(!(wildcard in wildcard_usage))
|
|
continue
|
|
|
|
wildcard_usage -= wildcard
|
|
access -= wildcard
|
|
wildcard_removed = TRUE
|
|
break
|
|
// Fallback to see if this was a force-added wildcard.
|
|
if(!wildcard_removed)
|
|
// If the card has no info for historic forced wildcards, that's an error state.
|
|
if(!wildcard_slots[WILDCARD_NAME_FORCED])
|
|
stack_trace("Wildcard ([wildcard]) could not be removed from [src]. This card has no forced wildcard data and the wildcard is not in this card's wildcard lists.")
|
|
|
|
var/list/wildcard_info = wildcard_slots[WILDCARD_NAME_FORCED]
|
|
var/wildcard_usage = wildcard_info["usage"]
|
|
|
|
if(!(wildcard in wildcard_usage))
|
|
stack_trace("Wildcard ([wildcard]) could not be removed from [src]. This access is not a wildcard on this card.")
|
|
|
|
wildcard_usage -= wildcard
|
|
access -= wildcard
|
|
wildcard_info["limit"] = length(wildcard_usage)
|
|
|
|
if(!wildcard_info["limit"])
|
|
wildcard_slots -= WILDCARD_NAME_FORCED
|
|
|
|
/**
|
|
* Attempts to add the given accesses to the ID card as non-wildcards.
|
|
*
|
|
* Depending on the mode, may add accesses as wildcards or error if it can't add them as non-wildcards.
|
|
* Arguments:
|
|
* * add_accesses - List of accesses to check.
|
|
* * try_wildcard - If not null, will attempt to add all accesses that require wildcard slots to this wildcard slot only.
|
|
* * mode - The method to use when adding accesses. See define for ERROR_ON_FAIL
|
|
*/
|
|
/obj/item/card/id/proc/add_access(list/add_accesses, try_wildcard = null, mode = ERROR_ON_FAIL)
|
|
var/list/wildcard_access = list()
|
|
var/list/normal_access = list()
|
|
|
|
build_access_lists(add_accesses, normal_access, wildcard_access)
|
|
|
|
// Check if we can add the wildcards.
|
|
if(mode == ERROR_ON_FAIL)
|
|
if(!can_add_wildcards(wildcard_access, try_wildcard))
|
|
CRASH("Cannot add wildcards from \[[add_accesses.Join(",")]\] to [src]")
|
|
|
|
// All clear to add the accesses.
|
|
access |= normal_access
|
|
if(mode != TRY_ADD_ALL_NO_WILDCARD)
|
|
add_wildcards(wildcard_access, try_wildcard, mode = mode)
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* Removes the given accesses from the ID Card.
|
|
*
|
|
* Will remove the wildcards if the accesses given are on the card as wildcard accesses.
|
|
* Arguments:
|
|
* * rem_accesses - List of accesses to remove.
|
|
*/
|
|
/obj/item/card/id/proc/remove_access(list/rem_accesses)
|
|
var/list/wildcard_access = list()
|
|
var/list/normal_access = list()
|
|
|
|
build_access_lists(rem_accesses, normal_access, wildcard_access)
|
|
|
|
access -= normal_access
|
|
remove_wildcards(wildcard_access)
|
|
|
|
/**
|
|
* Attempts to set the card's accesses to the given accesses, clearing all accesses not in the given list.
|
|
*
|
|
* Depending on the mode, may add accesses as wildcards or error if it can't add them as non-wildcards.
|
|
* Arguments:
|
|
* * new_access_list - List of all accesses that this card should hold exclusively.
|
|
* * mode - The method to use when setting accesses. See define for ERROR_ON_FAIL
|
|
*/
|
|
/obj/item/card/id/proc/set_access(list/new_access_list, mode = ERROR_ON_FAIL)
|
|
var/list/wildcard_access = list()
|
|
var/list/normal_access = list()
|
|
|
|
if(length(new_access_list))
|
|
build_access_lists(new_access_list, normal_access, wildcard_access)
|
|
|
|
// Check if we can add the wildcards.
|
|
if(mode == ERROR_ON_FAIL)
|
|
if(!can_add_wildcards(wildcard_access))
|
|
CRASH("Cannot add wildcards from \[[new_access_list.Join(",")]\] to [src]")
|
|
|
|
clear_access()
|
|
|
|
access = normal_access.Copy()
|
|
|
|
if(mode != TRY_ADD_ALL_NO_WILDCARD)
|
|
add_wildcards(wildcard_access, mode = mode)
|
|
|
|
return TRUE
|
|
|
|
/// Clears all accesses from the ID card - both wildcard and normal.
|
|
/obj/item/card/id/proc/clear_access()
|
|
// Go through the wildcards and reset them.
|
|
for(var/flag_name in wildcard_slots)
|
|
var/list/wildcard_info = wildcard_slots[flag_name]
|
|
var/list/wildcard_usage = wildcard_info["usage"]
|
|
wildcard_usage.Cut()
|
|
|
|
// Hard reset access
|
|
access.Cut()
|
|
|
|
/// Clears the economy account from the ID card.
|
|
/obj/item/card/id/proc/clear_account()
|
|
registered_account = null
|
|
|
|
|
|
/**
|
|
* Helper proc. Creates access lists for the access procs.
|
|
*
|
|
* Takes the accesses list and compares it with the trim. Any basic accesses that match the trim are
|
|
* added to basic_access_list and the rest are added to wildcard_access_list.
|
|
|
|
* This proc directly modifies the lists passed in as args. It expects these lists to be instantiated.
|
|
* There is no return value.
|
|
* Arguments:
|
|
* * accesses - List of accesses you want to stort into basic_access_list and wildcard_access_list. Should not be null.
|
|
* * basic_access_list - Mandatory argument. The proc modifies the list passed in this argument and adds accesses the trim supports to it.
|
|
* * wildcard_access_list - Mandatory argument. The proc modifies the list passed in this argument and adds accesses the trim does not support to it.
|
|
*/
|
|
/obj/item/card/id/proc/build_access_lists(list/accesses, list/basic_access_list, list/wildcard_access_list)
|
|
if(!length(accesses) || isnull(basic_access_list) || isnull(wildcard_access_list))
|
|
CRASH("Invalid parameters passed to build_access_lists")
|
|
|
|
var/list/trim_accesses = trim?.access
|
|
|
|
// Populate the lists.
|
|
for(var/new_access in accesses)
|
|
if(new_access in trim_accesses)
|
|
basic_access_list |= new_access
|
|
continue
|
|
|
|
wildcard_access_list |= new_access
|
|
|
|
/// Helper proc that determines if a card can be used in certain types of payment transactions.
|
|
/obj/item/card/id/proc/can_be_used_in_payment(mob/living/user)
|
|
if(QDELETED(src) || isnull(registered_account?.account_job) || !isliving(user))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/item/card/id/attack_self(mob/user)
|
|
if(Adjacent(user))
|
|
var/minor
|
|
if(registered_name && registered_age && registered_age < AGE_MINOR)
|
|
minor = " <b>[registered_age]</b>" //SKYRAT EDIT CHANGE
|
|
user.visible_message(span_notice("[user] shows you: [icon2html(src, viewers(user))] [src.name][minor]."), span_notice("You show \the [src.name][minor]."))
|
|
add_fingerprint(user)
|
|
|
|
/obj/item/card/id/afterattack_secondary(atom/target, mob/user, proximity_flag, click_parameters)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
if(!proximity_flag || !check_allowed_items(target) || !isfloorturf(target))
|
|
return
|
|
try_project_paystand(user, target)
|
|
|
|
/obj/item/card/id/attack_self_secondary(mob/user, modifiers)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return
|
|
try_project_paystand(user)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/item/card/id/add_context(atom/source, list/context, obj/item/held_item, mob/user)
|
|
. = ..()
|
|
|
|
context[SCREENTIP_CONTEXT_LMB] = "Show ID"
|
|
context[SCREENTIP_CONTEXT_RMB] = "Project pay stand"
|
|
if(isnull(registered_account) || registered_account.replaceable) //Same check we use when we check if we can assign an account
|
|
context[SCREENTIP_CONTEXT_ALT_RMB] = "Assign account"
|
|
else if(registered_account.account_balance > 0)
|
|
context[SCREENTIP_CONTEXT_ALT_LMB] = "Withdraw credits"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
/obj/item/card/id/proc/try_project_paystand(mob/user, turf/target)
|
|
if(!COOLDOWN_FINISHED(src, last_holopay_projection))
|
|
balloon_alert(user, "still recharging")
|
|
return
|
|
if(can_be_used_in_payment(user))
|
|
balloon_alert(user, "no account!")
|
|
to_chat(user, span_warning("You need a valid bank account to do this."))
|
|
return
|
|
/// Determines where the holopay will be placed based on tile contents
|
|
var/turf/projection
|
|
var/turf/step_ahead = get_step(user, user.dir)
|
|
var/turf/user_loc = user.loc
|
|
if(target && can_proj_holopay(target))
|
|
projection = target
|
|
else if(can_proj_holopay(step_ahead))
|
|
projection = step_ahead
|
|
else if(can_proj_holopay(user_loc))
|
|
projection = user_loc
|
|
if(!projection)
|
|
balloon_alert(user, "no space")
|
|
to_chat(user, span_warning("You need to be standing on or near an open tile to do this."))
|
|
return
|
|
/// Success: Valid tile for holopay placement
|
|
if(my_store)
|
|
my_store.dissipate()
|
|
var/obj/structure/holopay/new_store = new(projection)
|
|
if(new_store?.assign_card(projection, src))
|
|
COOLDOWN_START(src, last_holopay_projection, HOLOPAY_PROJECTION_INTERVAL)
|
|
playsound(projection, "sound/effects/empulse.ogg", 40, TRUE)
|
|
my_store = new_store
|
|
|
|
/**
|
|
* Determines whether a new holopay can be placed on the given turf.
|
|
* Checks if there are dense contents, too many contents, or another
|
|
* holopay already exists on the turf.
|
|
*
|
|
* Arguments:
|
|
* * turf/target - The target turf to be checked for dense contents
|
|
* Returns:
|
|
* * TRUE if the target is a valid holopay location, FALSE otherwise.
|
|
*/
|
|
/obj/item/card/id/proc/can_proj_holopay(turf/target)
|
|
if(!isfloorturf(target))
|
|
return FALSE
|
|
if(target.density)
|
|
return FALSE
|
|
if(length(target.contents) > 5)
|
|
return FALSE
|
|
for(var/obj/checked_obj in target.contents)
|
|
if(checked_obj.density)
|
|
return FALSE
|
|
if(istype(checked_obj, /obj/structure/holopay))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* Setter for the shop logo on linked holopays
|
|
*
|
|
* Arguments:
|
|
* * new_logo - The new logo to be set.
|
|
*/
|
|
/obj/item/card/id/proc/set_holopay_logo(new_logo)
|
|
if(!available_logos.Find(new_logo))
|
|
CRASH("User input a holopay shop logo that didn't exist.")
|
|
holopay_logo = new_logo
|
|
|
|
/**
|
|
* Setter for changing the force fee on a holopay.
|
|
*
|
|
* Arguments:
|
|
* * new_fee - The new fee to be set.
|
|
*/
|
|
/obj/item/card/id/proc/set_holopay_fee(new_fee)
|
|
if(!isnum(new_fee))
|
|
CRASH("User input a non number into the holopay fee field.")
|
|
if(new_fee < holopay_min_fee || new_fee > holopay_max_fee)
|
|
CRASH("User input a number outside of the valid range into the holopay fee field.")
|
|
holopay_fee = new_fee
|
|
|
|
/**
|
|
* Setter for changing the holopay name.
|
|
*
|
|
* Arguments:
|
|
* * new_name - The new name to be set.
|
|
*/
|
|
/obj/item/card/id/proc/set_holopay_name(name)
|
|
if(length(name) < 3 || length(name) > MAX_NAME_LEN)
|
|
to_chat(usr, span_warning("Must be between 3 - 42 characters."))
|
|
else
|
|
holopay_name = html_encode(trim(name, MAX_NAME_LEN))
|
|
|
|
|
|
/obj/item/card/id/vv_edit_var(var_name, var_value)
|
|
. = ..()
|
|
if(.)
|
|
switch(var_name)
|
|
if(NAMEOF(src, assignment), NAMEOF(src, registered_name), NAMEOF(src, registered_age))
|
|
update_label()
|
|
update_icon()
|
|
if(NAMEOF(src, trim))
|
|
if(ispath(trim))
|
|
SSid_access.apply_trim_to_card(src, trim)
|
|
|
|
/obj/item/card/id/attackby(obj/item/W, mob/user, params)
|
|
if(istype(W, /obj/item/rupee))
|
|
to_chat(user, span_warning("Your ID smartly rejects the strange shard of glass. Who knew, apparently it's not ACTUALLY valuable!"))
|
|
return
|
|
else if(iscash(W))
|
|
insert_money(W, user)
|
|
return
|
|
else if(istype(W, /obj/item/storage/bag/money))
|
|
var/obj/item/storage/bag/money/money_bag = W
|
|
var/list/money_contained = money_bag.contents
|
|
var/money_added = mass_insert_money(money_contained, user)
|
|
if (money_added)
|
|
to_chat(user, span_notice("You stuff the contents into the card! They disappear in a puff of bluespace smoke, adding [money_added] worth of credits to the linked account."))
|
|
return
|
|
/// SKYRAT EDIT BEGINS - Trim Tokens - Proc defined in modular_skyrat/modules/trim_tokens/code/cards_id.dm
|
|
else if(istype(W, /obj/item/trim_token))
|
|
apply_token(W, user)
|
|
return
|
|
/// SKYRAT EDIT ENDS
|
|
else
|
|
return ..()
|
|
|
|
/**
|
|
* Insert credits or coins into the ID card and add their value to the associated bank account.
|
|
*
|
|
* Arguments:
|
|
* money - The item to attempt to convert to credits and insert into the card.
|
|
* user - The user inserting the item.
|
|
* physical_currency - Boolean, whether this is a physical currency such as a coin and not a holochip.
|
|
*/
|
|
/obj/item/card/id/proc/insert_money(obj/item/money, mob/user)
|
|
var/physical_currency
|
|
if(istype(money, /obj/item/stack/spacecash) || istype(money, /obj/item/coin))
|
|
physical_currency = TRUE
|
|
|
|
if(!registered_account)
|
|
to_chat(user, span_warning("[src] doesn't have a linked account to deposit [money] into!"))
|
|
return
|
|
var/cash_money = money.get_item_credit_value()
|
|
if(!cash_money)
|
|
to_chat(user, span_warning("[money] doesn't seem to be worth anything!"))
|
|
return
|
|
registered_account.adjust_money(cash_money, "System: Deposit")
|
|
SSblackbox.record_feedback("amount", "credits_inserted", cash_money)
|
|
log_econ("[cash_money] credits were inserted into [src] owned by [src.registered_name]")
|
|
if(physical_currency)
|
|
to_chat(user, span_notice("You stuff [money] into [src]. It disappears in a small puff of bluespace smoke, adding [cash_money] credits to the linked account."))
|
|
else
|
|
to_chat(user, span_notice("You insert [money] into [src], adding [cash_money] credits to the linked account."))
|
|
|
|
to_chat(user, span_notice("The linked account now reports a balance of [registered_account.account_balance] cr."))
|
|
qdel(money)
|
|
|
|
/**
|
|
* Insert multiple money or money-equivalent items at once.
|
|
*
|
|
* Arguments:
|
|
* money - List of items to attempt to convert to credits and insert into the card.
|
|
* user - The user inserting the items.
|
|
*/
|
|
/obj/item/card/id/proc/mass_insert_money(list/money, mob/user)
|
|
if(!registered_account)
|
|
to_chat(user, span_warning("[src] doesn't have a linked account to deposit into!"))
|
|
return FALSE
|
|
|
|
if (!money || !length(money))
|
|
return FALSE
|
|
|
|
var/total = 0
|
|
|
|
for (var/obj/item/physical_money in money)
|
|
total += physical_money.get_item_credit_value()
|
|
CHECK_TICK
|
|
|
|
registered_account.adjust_money(total, "System: Deposit")
|
|
SSblackbox.record_feedback("amount", "credits_inserted", total)
|
|
log_econ("[total] credits were inserted into [src] owned by [src.registered_name]")
|
|
QDEL_LIST(money)
|
|
|
|
return total
|
|
|
|
/// Helper proc. Can the user alt-click the ID?
|
|
/obj/item/card/id/proc/alt_click_can_use_id(mob/living/user)
|
|
if(!isliving(user))
|
|
return FALSE
|
|
if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/// Attempts to set a new bank account on the ID card.
|
|
/obj/item/card/id/proc/set_new_account(mob/living/user)
|
|
. = FALSE
|
|
var/datum/bank_account/old_account = registered_account
|
|
if(loc != user)
|
|
to_chat(user, span_warning("You must be holding the ID to continue!"))
|
|
return FALSE
|
|
var/list/user_memories = user.mind.memories
|
|
var/datum/memory/key/account/user_key = user_memories[/datum/memory/key/account]
|
|
var/default_account = (istype(user_key) && user_key.remembered_id) || 11111
|
|
var/new_bank_id = tgui_input_number(user, "Enter the account ID to associate with this card.", "Link Bank Account", default_account, 999999, 111111)
|
|
if(!new_bank_id || QDELETED(user) || QDELETED(src) || issilicon(user) || !alt_click_can_use_id(user) || loc != user)
|
|
return FALSE
|
|
if(registered_account?.account_id == new_bank_id)
|
|
to_chat(user, span_warning("The account ID was already assigned to this card."))
|
|
return FALSE
|
|
var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[new_bank_id]"]
|
|
if(isnull(account))
|
|
to_chat(user, span_warning("The account ID number provided is invalid."))
|
|
return FALSE
|
|
if(old_account)
|
|
old_account.bank_cards -= src
|
|
account.account_balance += old_account.account_balance
|
|
account.bank_cards += src
|
|
registered_account = account
|
|
to_chat(user, span_notice("The provided account has been linked to this ID card. It contains [account.account_balance] credits."))
|
|
return TRUE
|
|
|
|
/obj/item/card/id/click_alt(mob/living/user)
|
|
if(!alt_click_can_use_id(user))
|
|
return NONE
|
|
if(registered_account.account_debt)
|
|
var/choice = tgui_alert(user, "Choose An Action", "Bank Account", list("Withdraw", "Pay Debt"))
|
|
if(!choice || QDELETED(user) || QDELETED(src) || !alt_click_can_use_id(user) || loc != user)
|
|
return CLICK_ACTION_BLOCKING
|
|
if(choice == "Pay Debt")
|
|
pay_debt(user)
|
|
return CLICK_ACTION_SUCCESS
|
|
if (registered_account.being_dumped)
|
|
registered_account.bank_card_talk(span_warning("内部服务器错误"), TRUE)
|
|
return CLICK_ACTION_SUCCESS
|
|
if(loc != user)
|
|
to_chat(user, span_warning("You must be holding the ID to continue!"))
|
|
return CLICK_ACTION_BLOCKING
|
|
if(registered_account.replaceable && !registered_account.account_balance)
|
|
var/choice = tgui_alert(user, "This card's account is unassigned. Would you like to link a bank account?", "Bank Account", list("Link Account", "Leave Unassigned"))
|
|
if(!choice || QDELETED(user) || QDELETED(src) || !alt_click_can_use_id(user) || loc != user)
|
|
return CLICK_ACTION_BLOCKING
|
|
if(choice == "Link Account")
|
|
set_new_account(user)
|
|
return CLICK_ACTION_SUCCESS
|
|
var/amount_to_remove = tgui_input_number(user, "How much do you want to withdraw? (Max: [registered_account.account_balance] cr)", "Withdraw Funds", max_value = registered_account.account_balance)
|
|
if(!amount_to_remove || QDELETED(user) || QDELETED(src) || issilicon(user) || loc != user)
|
|
return CLICK_ACTION_BLOCKING
|
|
if(!alt_click_can_use_id(user))
|
|
return CLICK_ACTION_BLOCKING
|
|
if(registered_account.adjust_money(-amount_to_remove, "System: Withdrawal"))
|
|
var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove)
|
|
user.put_in_hands(holochip)
|
|
to_chat(user, span_notice("You withdraw [amount_to_remove] credits into a holochip."))
|
|
SSblackbox.record_feedback("amount", "credits_removed", amount_to_remove)
|
|
log_econ("[amount_to_remove] credits were removed from [src] owned by [src.registered_name]")
|
|
return CLICK_ACTION_SUCCESS
|
|
else
|
|
var/difference = amount_to_remove - registered_account.account_balance
|
|
registered_account.bank_card_talk(span_warning("ERROR: The linked account requires [difference] more credit\s to perform that withdrawal."), TRUE)
|
|
return CLICK_ACTION_BLOCKING
|
|
|
|
/obj/item/card/id/alt_click_secondary(mob/user)
|
|
. = ..()
|
|
if(!alt_click_can_use_id(user))
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
if(!registered_account || registered_account.replaceable)
|
|
set_new_account(user)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
/obj/item/card/id/proc/pay_debt(user)
|
|
var/amount_to_pay = tgui_input_number(user, "How much do you want to pay? (Max: [registered_account.account_balance] cr)", "Debt Payment", max_value = min(registered_account.account_balance, registered_account.account_debt))
|
|
if(!amount_to_pay || QDELETED(src) || loc != user || !alt_click_can_use_id(user))
|
|
return
|
|
var/prev_debt = registered_account.account_debt
|
|
var/amount_paid = registered_account.pay_debt(amount_to_pay)
|
|
if(amount_paid)
|
|
var/message = span_notice("You pay [amount_to_pay] credits of a [prev_debt] cr debt. [registered_account.account_debt] cr to go.")
|
|
if(!registered_account.account_debt)
|
|
message = span_nicegreen("You pay the last [amount_to_pay] credits of your debt, extinguishing it. Congratulations!")
|
|
to_chat(user, message)
|
|
|
|
/obj/item/card/id/examine(mob/user)
|
|
. = ..()
|
|
if(!user.can_read(src))
|
|
return
|
|
|
|
if(registered_account && !isnull(registered_account.account_id))
|
|
. += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr."
|
|
if(ACCESS_COMMAND in access)
|
|
var/datum/bank_account/linked_dept = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department)
|
|
. += "The [linked_dept.account_holder] linked to the ID reports a balance of [linked_dept.account_balance] cr."
|
|
else
|
|
. += span_notice("Alt-Right-Click the ID to set the linked bank account.")
|
|
|
|
if(HAS_TRAIT(user, TRAIT_ID_APPRAISER))
|
|
. += HAS_TRAIT(src, TRAIT_JOB_FIRST_ID_CARD) ? span_boldnotice("Hmm... yes, this ID was issued from Central Command!") : span_boldnotice("This ID was created in this sector, not by Central Command.")
|
|
if(HAS_TRAIT(src, TRAIT_TASTEFULLY_THICK_ID_CARD) && (user.is_holding(src) || (user.CanReach(src) && user.put_in_hands(src, ignore_animation = FALSE))))
|
|
ADD_TRAIT(src, TRAIT_NODROP, "psycho")
|
|
. += span_hypnophrase("Look at that subtle coloring... The tasteful thickness of it. Oh my God, it even has a watermark...")
|
|
var/sound/slowbeat = sound('sound/health/slowbeat.ogg', repeat = TRUE)
|
|
user.playsound_local(get_turf(src), slowbeat, 40, 0, channel = CHANNEL_HEARTBEAT, use_reverb = FALSE)
|
|
if(isliving(user))
|
|
var/mob/living/living_user = user
|
|
living_user.adjust_jitter(10 SECONDS)
|
|
addtimer(CALLBACK(src, PROC_REF(drop_card), user), 10 SECONDS)
|
|
. += span_notice("<i>There's more information below, you can look again to take a closer look...</i>")
|
|
|
|
/obj/item/card/id/proc/drop_card(mob/user)
|
|
user.stop_sound_channel(CHANNEL_HEARTBEAT)
|
|
REMOVE_TRAIT(src, TRAIT_NODROP, "psycho")
|
|
if(user.is_holding(src))
|
|
user.dropItemToGround(src)
|
|
for(var/mob/living/carbon/human/viewing_mob in viewers(user, 2))
|
|
if(viewing_mob.stat || viewing_mob == user)
|
|
continue
|
|
viewing_mob.say("Is something wrong? [user.first_name()]... you're sweating.", forced = "psycho")
|
|
break
|
|
|
|
/obj/item/card/id/examine_more(mob/user)
|
|
. = ..()
|
|
if(!user.can_read(src))
|
|
return
|
|
|
|
. += span_notice("<i>You examine [src] closer, and note the following...</i>")
|
|
|
|
if(registered_age)
|
|
. += "The card indicates that the holder is [registered_age] years old. [(registered_age < AGE_MINOR) ? "There's a holographic stripe that reads <b>[span_danger("'MINOR: DO NOT SERVE ALCOHOL OR TOBACCO'")]</b> along the bottom of the card." : ""]"
|
|
if(registered_account)
|
|
if(registered_account.mining_points)
|
|
. += "There's [registered_account.mining_points] mining point\s loaded onto the card's bank account."
|
|
. += "The account linked to the ID belongs to '[registered_account.account_holder]' and reports a balance of [registered_account.account_balance] cr."
|
|
if(registered_account.account_debt)
|
|
. += span_warning("The account is currently indebted for [registered_account.account_debt] cr. [100*DEBT_COLLECTION_COEFF]% of all earnings will go towards extinguishing it.")
|
|
if(registered_account.account_job)
|
|
var/datum/bank_account/D = SSeconomy.get_dep_account(registered_account.account_job.paycheck_department)
|
|
if(D)
|
|
. += "The [D.account_holder] reports a balance of [D.account_balance] cr."
|
|
. += span_info("Alt-Click the ID to pull money from the linked account in the form of holochips.")
|
|
. += span_info("You can insert credits into the linked account by pressing holochips, cash, or coins against the ID.")
|
|
if(registered_account.replaceable)
|
|
. += span_info("Alt-Right-Click the ID to change the linked bank account.")
|
|
if(registered_account.civilian_bounty)
|
|
. += "<span class='info'><b>There is an active civilian bounty.</b>"
|
|
. += span_info("<i>[registered_account.bounty_text()]</i>")
|
|
. += span_info("Quantity: [registered_account.bounty_num()]")
|
|
. += span_info("Reward: [registered_account.bounty_value()]")
|
|
if(registered_account.account_holder == user.real_name)
|
|
. += span_boldnotice("If you lose this ID card, you can reclaim your account by Alt-Clicking a blank ID card while holding it and entering your account ID number.")
|
|
else
|
|
. += span_info("There is no registered account linked to this card. Alt-Click to add one.")
|
|
|
|
return .
|
|
|
|
/obj/item/card/id/GetAccess()
|
|
return access.Copy()
|
|
|
|
/obj/item/card/id/GetID()
|
|
return src
|
|
|
|
/obj/item/card/id/RemoveID()
|
|
return src
|
|
|
|
/// Called on COMSIG_ATOM_UPDATED_ICON. Updates the visuals of the wallet this card is in.
|
|
/obj/item/card/id/proc/update_in_wallet()
|
|
SIGNAL_HANDLER
|
|
|
|
if(istype(loc, /obj/item/storage/wallet))
|
|
var/obj/item/storage/wallet/powergaming = loc
|
|
if(powergaming.front_id == src)
|
|
powergaming.update_label()
|
|
powergaming.update_appearance()
|
|
|
|
/// Updates the name based on the card's vars and state.
|
|
/obj/item/card/id/proc/update_label()
|
|
var/name_string = registered_name ? "[registered_name]'s ID Card" : initial(name)
|
|
var/assignment_string
|
|
|
|
if(is_intern)
|
|
if(assignment)
|
|
assignment_string = trim?.intern_alt_name || "Intern [assignment]"
|
|
else
|
|
assignment_string = "Intern"
|
|
else
|
|
assignment_string = assignment
|
|
|
|
name = "[name_string] ([assignment_string])"
|
|
|
|
/// Returns the trim assignment name.
|
|
/obj/item/card/id/proc/get_trim_assignment()
|
|
return trim?.assignment || assignment
|
|
|
|
/// Returns the trim sechud icon state.
|
|
/obj/item/card/id/proc/get_trim_sechud_icon_state()
|
|
return trim?.sechud_icon_state || SECHUD_UNKNOWN
|
|
|
|
/obj/item/card/id/away
|
|
name = "\proper a perfectly generic identification card"
|
|
desc = "A perfectly generic identification card. Looks like it could use some flavor."
|
|
trim = /datum/id_trim/away
|
|
icon_state = "retro"
|
|
registered_age = null
|
|
|
|
/obj/item/card/id/away/hotel
|
|
name = "Staff ID"
|
|
desc = "A staff ID used to access the hotel's doors."
|
|
trim = /datum/id_trim/away/hotel
|
|
|
|
/obj/item/card/id/away/hotel/security
|
|
name = "Officer ID"
|
|
trim = /datum/id_trim/away/hotel/security
|
|
|
|
/obj/item/card/id/away/old
|
|
name = "\proper a perfectly generic identification card"
|
|
desc = "A perfectly generic identification card. Looks like it could use some flavor."
|
|
|
|
/obj/item/card/id/away/old/sec
|
|
name = "Charlie Station Security Officer's ID card"
|
|
desc = "A faded Charlie Station ID card. You can make out the rank \"Security Officer\"."
|
|
trim = /datum/id_trim/away/old/sec
|
|
|
|
/obj/item/card/id/away/old/sci
|
|
name = "Charlie Station Scientist's ID card"
|
|
desc = "A faded Charlie Station ID card. You can make out the rank \"Scientist\"."
|
|
trim = /datum/id_trim/away/old/sci
|
|
|
|
/obj/item/card/id/away/old/eng
|
|
name = "Charlie Station Engineer's ID card"
|
|
desc = "A faded Charlie Station ID card. You can make out the rank \"Station Engineer\"."
|
|
trim = /datum/id_trim/away/old/eng
|
|
|
|
/obj/item/card/id/away/old/equipment
|
|
name = "Engineering Equipment Access"
|
|
desc = "A special ID card that allows access to engineering equipment."
|
|
trim = /datum/id_trim/away/old/equipment
|
|
|
|
/obj/item/card/id/away/old/robo
|
|
name = "Delta Station Roboticist's ID card"
|
|
desc = "An ID card that allows access to bots maintenance protocols."
|
|
trim = /datum/id_trim/away/old/robo
|
|
|
|
/obj/item/card/id/away/deep_storage //deepstorage.dmm space ruin
|
|
name = "bunker access ID"
|
|
|
|
/obj/item/card/id/departmental_budget
|
|
name = "departmental card (ERROR)"
|
|
desc = "Provides access to the departmental budget."
|
|
icon_state = "budgetcard"
|
|
var/department_ID = ACCOUNT_CIV
|
|
var/department_name = ACCOUNT_CIV_NAME
|
|
registered_age = null
|
|
|
|
/obj/item/card/id/departmental_budget/Initialize(mapload)
|
|
. = ..()
|
|
var/datum/bank_account/B = SSeconomy.get_dep_account(department_ID)
|
|
if(B)
|
|
registered_account = B
|
|
if(!B.bank_cards.Find(src))
|
|
B.bank_cards += src
|
|
name = "departmental card ([department_name])"
|
|
desc = "Provides access to the [department_name]."
|
|
SSeconomy.dep_cards += src
|
|
|
|
/obj/item/card/id/departmental_budget/Destroy()
|
|
SSeconomy.dep_cards -= src
|
|
return ..()
|
|
|
|
/obj/item/card/id/departmental_budget/update_label()
|
|
return
|
|
|
|
/obj/item/card/id/departmental_budget/car
|
|
department_ID = ACCOUNT_CAR
|
|
department_name = ACCOUNT_CAR_NAME
|
|
icon_state = "car_budget" //saving up for a new tesla
|
|
|
|
/obj/item/card/id/departmental_budget/click_alt(mob/living/user)
|
|
registered_account.bank_card_talk(span_warning("Withdrawing is not compatible with this card design."), TRUE) //prevents the vault bank machine being useless and putting money from the budget to your card to go over personal crates
|
|
return CLICK_ACTION_BLOCKING
|
|
|
|
/obj/item/card/id/advanced
|
|
name = "identification card"
|
|
desc = "A card used to provide ID and determine access across the station. Has an integrated digital display and advanced microchips."
|
|
icon_state = "card_grey"
|
|
|
|
wildcard_slots = WILDCARD_LIMIT_GREY
|
|
flags_1 = UNPAINTABLE_1
|
|
|
|
/// An overlay icon state for when the card is assigned to a name. Usually manifests itself as a little scribble to the right of the job icon.
|
|
var/assigned_icon_state = "assigned"
|
|
|
|
/// If this is set, will manually override the icon file for the trim. Intended for admins to VV edit and chameleon ID cards.
|
|
var/trim_icon_override
|
|
/// If this is set, will manually override the icon state for the trim. Intended for admins to VV edit and chameleon ID cards.
|
|
var/trim_state_override
|
|
/// If this is set, will manually override the department color for this trim. Intended for admins to VV edit and chameleon ID cards.
|
|
var/department_color_override
|
|
/// If this is set, will manually override the department icon state for the trim. Intended for admins to VV edit and chameleon ID cards.
|
|
var/department_state_override
|
|
/// If this is set, will manually override the subdepartment color for this trim. Intended for admins to VV edit and chameleon ID cards.
|
|
var/subdepartment_color_override
|
|
/// If this is set, will manually override the trim's assignmment as it appears in the crew monitor and elsewhere. Intended for admins to VV edit and chameleon ID cards.
|
|
var/trim_assignment_override
|
|
/// If this is set, will manually override the trim shown for SecHUDs. Intended for admins to VV edit and chameleon ID cards.
|
|
var/sechud_icon_state_override = null
|
|
|
|
/obj/item/card/id/advanced/Initialize(mapload)
|
|
. = ..()
|
|
RegisterSignal(src, COMSIG_ITEM_EQUIPPED, PROC_REF(update_intern_status))
|
|
RegisterSignal(src, COMSIG_ITEM_DROPPED, PROC_REF(remove_intern_status))
|
|
|
|
/obj/item/card/id/advanced/Destroy()
|
|
UnregisterSignal(src, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
|
|
|
|
return ..()
|
|
|
|
|
|
/obj/item/card/id/advanced/attackby(obj/item/W, mob/user, params)
|
|
. = ..()
|
|
if(istype(W, /obj/item/toy/crayon))
|
|
var/obj/item/toy/crayon/our_crayon = W
|
|
if(tgui_alert(usr, "Recolor Department or Subdepartment?", "Recoloring ID...", list("Department", "Subdepartment")) == "Department")
|
|
if(!do_after(user, 2 SECONDS)) // Doesn't technically require a spraycan's cap to be off but shhh
|
|
return
|
|
department_color_override = our_crayon.paint_color
|
|
balloon_alert(user, "recolored")
|
|
else if(do_after(user, 1 SECONDS))
|
|
subdepartment_color_override = our_crayon.paint_color
|
|
balloon_alert(user, "recolored")
|
|
update_icon()
|
|
|
|
/obj/item/card/id/advanced/proc/update_intern_status(datum/source, mob/user, slot)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!user?.client)
|
|
return
|
|
if(!CONFIG_GET(flag/use_exp_tracking))
|
|
return
|
|
if(!CONFIG_GET(flag/use_low_living_hour_intern))
|
|
return
|
|
if(!SSdbcore.Connect())
|
|
return
|
|
|
|
var/intern_threshold = (CONFIG_GET(number/use_low_living_hour_intern_hours) * 60) || (CONFIG_GET(number/use_exp_restrictions_heads_hours) * 60) || INTERN_THRESHOLD_FALLBACK_HOURS * 60
|
|
var/playtime = user.client.get_exp_living(pure_numeric = TRUE)
|
|
|
|
if((intern_threshold >= playtime) && (user.mind?.assigned_role.job_flags & JOB_CAN_BE_INTERN))
|
|
is_intern = TRUE
|
|
update_label()
|
|
return
|
|
|
|
if(!is_intern)
|
|
return
|
|
|
|
is_intern = FALSE
|
|
update_label()
|
|
|
|
/obj/item/card/id/advanced/proc/remove_intern_status(datum/source, mob/user)
|
|
SIGNAL_HANDLER
|
|
|
|
if(!is_intern)
|
|
return
|
|
|
|
is_intern = FALSE
|
|
update_label()
|
|
|
|
/obj/item/card/id/advanced/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
|
|
. = ..()
|
|
|
|
//Old loc
|
|
if(istype(old_loc, /obj/item/storage/wallet))
|
|
UnregisterSignal(old_loc, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
|
|
|
|
if(istype(old_loc, /obj/item/modular_computer))
|
|
UnregisterSignal(old_loc, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
|
|
|
|
//New loc
|
|
if(istype(loc, /obj/item/storage/wallet))
|
|
RegisterSignal(loc, COMSIG_ITEM_EQUIPPED, PROC_REF(update_intern_status))
|
|
RegisterSignal(loc, COMSIG_ITEM_DROPPED, PROC_REF(remove_intern_status))
|
|
|
|
if(istype(loc, /obj/item/modular_computer))
|
|
RegisterSignal(loc, COMSIG_ITEM_EQUIPPED, PROC_REF(update_intern_status))
|
|
RegisterSignal(loc, COMSIG_ITEM_DROPPED, PROC_REF(remove_intern_status))
|
|
|
|
/obj/item/card/id/advanced/update_overlays()
|
|
. = ..()
|
|
|
|
if(registered_name && registered_name != "Captain")
|
|
. += mutable_appearance(icon, assigned_icon_state)
|
|
|
|
var/trim_icon_file = trim_icon_override ? trim_icon_override : trim?.trim_icon
|
|
var/trim_icon_state = trim_state_override ? trim_state_override : trim?.trim_state
|
|
var/trim_department_color = department_color_override ? department_color_override : trim?.department_color
|
|
var/trim_department_state = department_state_override ? department_state_override : trim?.department_state
|
|
var/trim_subdepartment_color = subdepartment_color_override ? subdepartment_color_override : trim?.subdepartment_color
|
|
|
|
if(!trim_icon_file || !trim_icon_state || !trim_department_color || !trim_subdepartment_color || !trim_department_state)
|
|
return
|
|
|
|
/// We handle department and subdepartment overlays first, so the job icon is always on top.
|
|
var/mutable_appearance/department_overlay = mutable_appearance(trim_icon_file, trim_department_state)
|
|
department_overlay.color = trim_department_color
|
|
. += department_overlay
|
|
|
|
var/mutable_appearance/subdepartment_overlay = mutable_appearance(trim_icon_file, "subdepartment")
|
|
subdepartment_overlay.color = trim_subdepartment_color
|
|
. += subdepartment_overlay
|
|
|
|
/// Then we handle the job's icon here.
|
|
. += mutable_appearance(trim_icon_file, trim_icon_state)
|
|
|
|
/obj/item/card/id/advanced/get_trim_assignment()
|
|
if(trim_assignment_override)
|
|
return trim_assignment_override
|
|
|
|
if(ispath(trim))
|
|
var/datum/id_trim/trim_singleton = SSid_access.trim_singletons_by_path[trim]
|
|
return trim_singleton.assignment
|
|
|
|
return ..()
|
|
|
|
/// Returns the trim sechud icon state.
|
|
/obj/item/card/id/advanced/get_trim_sechud_icon_state()
|
|
return sechud_icon_state_override || ..()
|
|
|
|
/obj/item/card/id/advanced/rainbow
|
|
name = "rainbow identification card"
|
|
desc = "A rainbow card, promoting fun in a 'business proper' sense!"
|
|
icon_state = "card_rainbow"
|
|
|
|
/obj/item/card/id/advanced/silver
|
|
name = "silver identification card"
|
|
desc = "A silver card which shows honour and dedication."
|
|
icon_state = "card_silver"
|
|
inhand_icon_state = "silver_id"
|
|
assigned_icon_state = "assigned_silver"
|
|
wildcard_slots = WILDCARD_LIMIT_SILVER
|
|
|
|
/obj/item/card/id/advanced/robotic
|
|
name = "magnetic identification card"
|
|
desc = "An integrated card which shows the work poured into opening doors."
|
|
icon_state = "card_carp" //im not a spriter
|
|
inhand_icon_state = "silver_id"
|
|
assigned_icon_state = "assigned_silver"
|
|
wildcard_slots = WILDCARD_LIMIT_GREY
|
|
|
|
/datum/id_trim/maint_reaper
|
|
access = list(ACCESS_MAINT_TUNNELS)
|
|
trim_state = "trim_janitor"
|
|
assignment = "Reaper"
|
|
|
|
/obj/item/card/id/advanced/silver/reaper
|
|
name = "Thirteen's ID Card (Reaper)"
|
|
trim = /datum/id_trim/maint_reaper
|
|
registered_name = "Thirteen"
|
|
|
|
/obj/item/card/id/advanced/gold
|
|
name = "gold identification card"
|
|
desc = "A golden card which shows power and might."
|
|
icon_state = "card_gold"
|
|
inhand_icon_state = "gold_id"
|
|
assigned_icon_state = "assigned_gold"
|
|
wildcard_slots = WILDCARD_LIMIT_GOLD
|
|
|
|
/obj/item/card/id/advanced/gold/Initialize(mapload)
|
|
. = ..()
|
|
ADD_TRAIT(src, TRAIT_TASTEFULLY_THICK_ID_CARD, ROUNDSTART_TRAIT)
|
|
|
|
/obj/item/card/id/advanced/gold/captains_spare
|
|
name = "captain's spare ID"
|
|
desc = "The spare ID of the High Lord himself."
|
|
registered_name = "Captain"
|
|
trim = /datum/id_trim/job/captain
|
|
registered_age = null
|
|
|
|
/obj/item/card/id/advanced/gold/captains_spare/update_label() //so it doesn't change to Captain's ID card (Captain) on a sneeze
|
|
if(registered_name == "Captain")
|
|
name = "[initial(name)][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]"
|
|
update_appearance(UPDATE_ICON)
|
|
else
|
|
..()
|
|
|
|
/obj/item/card/id/advanced/centcom
|
|
name = "\improper CentCom ID"
|
|
desc = "An ID straight from Central Command."
|
|
icon_state = "card_centcom"
|
|
assigned_icon_state = "assigned_centcom"
|
|
registered_name = JOB_CENTCOM
|
|
registered_age = null
|
|
trim = /datum/id_trim/centcom
|
|
wildcard_slots = WILDCARD_LIMIT_CENTCOM
|
|
|
|
/obj/item/card/id/advanced/centcom/ert
|
|
name = "\improper CentCom ID"
|
|
desc = "An ERT ID card."
|
|
registered_age = null
|
|
registered_name = "Emergency Response Intern"
|
|
trim = /datum/id_trim/centcom/ert
|
|
|
|
/obj/item/card/id/advanced/centcom/ert
|
|
registered_name = JOB_ERT_COMMANDER
|
|
trim = /datum/id_trim/centcom/ert/commander
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/security
|
|
registered_name = JOB_ERT_OFFICER
|
|
trim = /datum/id_trim/centcom/ert/security
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/engineer
|
|
registered_name = JOB_ERT_ENGINEER
|
|
trim = /datum/id_trim/centcom/ert/engineer
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/medical
|
|
registered_name = JOB_ERT_MEDICAL_DOCTOR
|
|
trim = /datum/id_trim/centcom/ert/medical
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/chaplain
|
|
registered_name = JOB_ERT_CHAPLAIN
|
|
trim = /datum/id_trim/centcom/ert/chaplain
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/janitor
|
|
registered_name = JOB_ERT_JANITOR
|
|
trim = /datum/id_trim/centcom/ert/janitor
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/clown
|
|
registered_name = JOB_ERT_CLOWN
|
|
trim = /datum/id_trim/centcom/ert/clown
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/militia
|
|
registered_name = "Frontier Militia"
|
|
trim = /datum/id_trim/centcom/ert/militia
|
|
|
|
/obj/item/card/id/advanced/centcom/ert/militia/general
|
|
registered_name = "Frontier Militia General"
|
|
trim = /datum/id_trim/centcom/ert/militia/general
|
|
|
|
/obj/item/card/id/advanced/black
|
|
name = "black identification card"
|
|
desc = "This card is telling you one thing and one thing alone. The person holding this card is an utter badass."
|
|
icon_state = "card_black"
|
|
assigned_icon_state = "assigned_syndicate"
|
|
wildcard_slots = WILDCARD_LIMIT_GOLD
|
|
|
|
/obj/item/card/id/advanced/black/deathsquad
|
|
name = "\improper Death Squad ID"
|
|
desc = "A Death Squad ID card."
|
|
registered_name = JOB_ERT_DEATHSQUAD
|
|
trim = /datum/id_trim/centcom/deathsquad
|
|
wildcard_slots = WILDCARD_LIMIT_DEATHSQUAD
|
|
|
|
/obj/item/card/id/advanced/black/syndicate_command
|
|
name = "syndicate ID card"
|
|
desc = "An ID straight from the Syndicate."
|
|
registered_name = "Syndicate"
|
|
registered_age = null
|
|
trim = /datum/id_trim/syndicom
|
|
wildcard_slots = WILDCARD_LIMIT_SYNDICATE
|
|
|
|
/obj/item/card/id/advanced/black/syndicate_command/crew_id
|
|
name = "syndicate ID card"
|
|
desc = "An ID straight from the Syndicate."
|
|
registered_name = "Syndicate"
|
|
trim = /datum/id_trim/syndicom/crew
|
|
|
|
/obj/item/card/id/advanced/black/syndicate_command/captain_id
|
|
name = "syndicate captain ID card"
|
|
desc = "An ID straight from the Syndicate."
|
|
registered_name = "Syndicate"
|
|
trim = /datum/id_trim/syndicom/captain
|
|
|
|
|
|
/obj/item/card/id/advanced/black/syndicate_command/captain_id/syndie_spare
|
|
name = "syndicate captain's spare ID"
|
|
desc = "The spare ID of the Dark Lord himself."
|
|
registered_name = "Captain"
|
|
registered_age = null
|
|
|
|
/obj/item/card/id/advanced/black/syndicate_command/captain_id/syndie_spare/update_label()
|
|
if(registered_name == "Captain")
|
|
name = "[initial(name)][(!assignment || assignment == "Captain") ? "" : " ([assignment])"]"
|
|
update_appearance(UPDATE_ICON)
|
|
return
|
|
|
|
return ..()
|
|
|
|
/obj/item/card/id/advanced/debug
|
|
name = "\improper Debug ID"
|
|
desc = "A debug ID card. Has ALL the all access and a boatload of money, you really shouldn't have this."
|
|
icon_state = "card_centcom"
|
|
assigned_icon_state = "assigned_centcom"
|
|
trim = /datum/id_trim/admin
|
|
wildcard_slots = WILDCARD_LIMIT_ADMIN
|
|
|
|
/obj/item/card/id/advanced/debug/Initialize(mapload)
|
|
. = ..()
|
|
registered_account = new(player_account = FALSE)
|
|
registered_account.account_id = ADMIN_ACCOUNT_ID // this is so bank_card_talk() can work.
|
|
registered_account.account_job = SSjob.GetJobType(/datum/job/admin)
|
|
registered_account.account_balance += 999999 // MONEY! We add more money to the account every time we spawn because it's a debug item and infinite money whoopie
|
|
|
|
/obj/item/card/id/advanced/debug/alt_click_can_use_id(mob/living/user)
|
|
. = ..()
|
|
if(!. || isnull(user.client?.holder)) // admins only as a safety so people don't steal all the dollars. spawn in a holochip if you want them to get some dosh
|
|
registered_account.bank_card_talk(span_warning("Only authorized representatives of Nanotrasen may use this card."), force = TRUE)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/item/card/id/advanced/debug/can_be_used_in_payment(mob/living/user)
|
|
. = ..()
|
|
if(!. || isnull(user.client?.holder))
|
|
registered_account.bank_card_talk(span_warning("Only authorized representatives of Nanotrasen may use this card."), force = TRUE)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/item/card/id/advanced/prisoner
|
|
name = "prisoner ID card"
|
|
desc = "You are a number, you are not a free man."
|
|
icon_state = "card_prisoner"
|
|
inhand_icon_state = "orange-id"
|
|
registered_name = "Scum"
|
|
registered_age = null
|
|
trim = /datum/id_trim/job/prisoner
|
|
|
|
wildcard_slots = WILDCARD_LIMIT_PRISONER
|
|
|
|
/// Number of gulag points required to earn freedom.
|
|
var/goal = 0
|
|
/// Number of gulag points earned.
|
|
var/points = 0
|
|
/// If the card has a timer set on it for temporary stay.
|
|
var/timed = FALSE
|
|
/// Time to assign to the card when they pass through the security gate.
|
|
var/time_to_assign
|
|
/// Time left on a card till they can leave.
|
|
var/time_left = 0
|
|
|
|
/obj/item/card/id/advanced/prisoner/attackby(obj/item/card/id/C, mob/user)
|
|
..()
|
|
var/list/id_access = C.GetAccess()
|
|
if(!(ACCESS_BRIG in id_access))
|
|
return FALSE
|
|
if(loc != user)
|
|
to_chat(user, span_warning("You must be holding the ID to continue!"))
|
|
return FALSE
|
|
if(timed)
|
|
timed = FALSE
|
|
time_to_assign = initial(time_to_assign)
|
|
registered_name = initial(registered_name)
|
|
STOP_PROCESSING(SSobj, src)
|
|
to_chat(user, "Restating prisoner ID to default parameters.")
|
|
return
|
|
var/choice = tgui_input_number(user, "Sentence time in seconds", "Sentencing")
|
|
if(!choice || QDELETED(user) || QDELETED(src) || !usr.can_perform_action(src, FORBID_TELEKINESIS_REACH) || loc != user)
|
|
return FALSE
|
|
time_to_assign = choice
|
|
to_chat(user, "You set the sentence time to [time_to_assign] seconds.")
|
|
timed = TRUE
|
|
|
|
/obj/item/card/id/advanced/prisoner/proc/start_timer()
|
|
say("Sentence started, welcome to the corporate rehabilitation center!")
|
|
START_PROCESSING(SSobj, src)
|
|
|
|
/obj/item/card/id/advanced/prisoner/examine(mob/user)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
|
|
if(timed)
|
|
if(time_left <= 0)
|
|
. += span_notice("The digital timer on the card has zero seconds remaining. You leave a changed man, but a free man nonetheless.")
|
|
else
|
|
. += span_notice("The digital timer on the card has [time_left] seconds remaining. Don't do the crime if you can't do the time.")
|
|
|
|
/obj/item/card/id/advanced/prisoner/process(seconds_per_tick)
|
|
if(!timed)
|
|
return
|
|
time_left -= seconds_per_tick
|
|
if(time_left <= 0)
|
|
say("Sentence time has been served. Thank you for your cooperation in our corporate rehabilitation program!")
|
|
STOP_PROCESSING(SSobj, src)
|
|
|
|
/obj/item/card/id/advanced/prisoner/attack_self(mob/user)
|
|
to_chat(usr, span_notice("You have accumulated [points] out of the [goal] points you need for freedom."))
|
|
|
|
/obj/item/card/id/advanced/prisoner/one
|
|
name = "Prisoner #13-001"
|
|
registered_name = "Prisoner #13-001"
|
|
trim = /datum/id_trim/job/prisoner/one
|
|
|
|
/obj/item/card/id/advanced/prisoner/two
|
|
name = "Prisoner #13-002"
|
|
registered_name = "Prisoner #13-002"
|
|
trim = /datum/id_trim/job/prisoner/two
|
|
|
|
/obj/item/card/id/advanced/prisoner/three
|
|
name = "Prisoner #13-003"
|
|
registered_name = "Prisoner #13-003"
|
|
trim = /datum/id_trim/job/prisoner/three
|
|
|
|
/obj/item/card/id/advanced/prisoner/four
|
|
name = "Prisoner #13-004"
|
|
registered_name = "Prisoner #13-004"
|
|
trim = /datum/id_trim/job/prisoner/four
|
|
|
|
/obj/item/card/id/advanced/prisoner/five
|
|
name = "Prisoner #13-005"
|
|
registered_name = "Prisoner #13-005"
|
|
trim = /datum/id_trim/job/prisoner/five
|
|
|
|
/obj/item/card/id/advanced/prisoner/six
|
|
name = "Prisoner #13-006"
|
|
registered_name = "Prisoner #13-006"
|
|
trim = /datum/id_trim/job/prisoner/six
|
|
|
|
/obj/item/card/id/advanced/prisoner/seven
|
|
name = "Prisoner #13-007"
|
|
registered_name = "Prisoner #13-007"
|
|
trim = /datum/id_trim/job/prisoner/seven
|
|
|
|
/obj/item/card/id/advanced/mining
|
|
name = "mining ID"
|
|
trim = /datum/id_trim/job/shaft_miner/spare
|
|
|
|
/obj/item/card/id/advanced/highlander
|
|
name = "highlander ID"
|
|
registered_name = "Highlander"
|
|
desc = "There can be only one!"
|
|
icon_state = "card_black"
|
|
assigned_icon_state = "assigned_syndicate"
|
|
trim = /datum/id_trim/highlander
|
|
wildcard_slots = WILDCARD_LIMIT_ADMIN
|
|
|
|
/obj/item/card/id/advanced/chameleon
|
|
name = "agent card"
|
|
desc = "A highly advanced chameleon ID card. Touch this card on another ID card or player to choose which accesses to copy. \
|
|
Has special magnetic properties which force it to the front of wallets."
|
|
trim = /datum/id_trim/chameleon
|
|
wildcard_slots = WILDCARD_LIMIT_CHAMELEON_PLUS // SKYRAT EDIT - Original WILDCARD_LIMIT_CHAMELEON
|
|
actions_types = list(/datum/action/item_action/chameleon/change/id, /datum/action/item_action/chameleon/change/id_trim)
|
|
|
|
/// Have we set a custom name and job assignment, or will we use what we're given when we chameleon change?
|
|
var/forged = FALSE
|
|
/// Anti-metagaming protections. If TRUE, anyone can change the ID card's details. If FALSE, only syndicate agents can.
|
|
var/anyone = FALSE
|
|
/// Weak ref to the ID card we're currently attempting to steal access from.
|
|
var/datum/weakref/theft_target
|
|
|
|
/obj/item/card/id/advanced/chameleon/Initialize(mapload)
|
|
. = ..()
|
|
register_item_context()
|
|
|
|
/obj/item/card/id/advanced/chameleon/Destroy()
|
|
theft_target = null
|
|
return ..()
|
|
|
|
/obj/item/card/id/advanced/chameleon/afterattack(atom/target, mob/user, proximity, click_parameters)
|
|
. = ..()
|
|
if(!proximity)
|
|
return
|
|
|
|
if(isidcard(target))
|
|
theft_target = WEAKREF(target)
|
|
ui_interact(user)
|
|
return . | AFTERATTACK_PROCESSED_ITEM
|
|
|
|
/obj/item/card/id/advanced/chameleon/pre_attack_secondary(atom/target, mob/living/user, params)
|
|
. = ..()
|
|
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
|
|
return .
|
|
|
|
// If we're attacking a human, we want it to be covert. We're not ATTACKING them, we're trying
|
|
// to sneakily steal their accesses by swiping our agent ID card near them. As a result, we
|
|
// return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN to cancel any part of the following the attack chain.
|
|
if(ishuman(target))
|
|
target.balloon_alert(user, "scanning ID card...")
|
|
|
|
if(!do_after(user, 2 SECONDS, target))
|
|
target.balloon_alert(user, "interrupted!")
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
var/mob/living/carbon/human/human_target = target
|
|
|
|
var/list/target_id_cards = human_target.get_all_contents_type(/obj/item/card/id)
|
|
|
|
if(!length(target_id_cards))
|
|
target.balloon_alert(user, "no IDs!")
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
var/selected_id = pick(target_id_cards)
|
|
target.balloon_alert(user, UNLINT("IDs synced"))
|
|
theft_target = WEAKREF(selected_id)
|
|
ui_interact(user)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
if(isitem(target))
|
|
var/obj/item/target_item = target
|
|
|
|
target.balloon_alert(user, "scanning ID card...")
|
|
|
|
var/list/target_id_cards = target_item.get_all_contents_type(/obj/item/card/id)
|
|
|
|
var/target_item_id = target_item.GetID()
|
|
|
|
if(target_item_id)
|
|
target_id_cards |= target_item_id
|
|
|
|
if(!length(target_id_cards))
|
|
target.balloon_alert(user, "no IDs!")
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
var/selected_id = pick(target_id_cards)
|
|
target.balloon_alert(user, UNLINT("IDs synced"))
|
|
theft_target = WEAKREF(selected_id)
|
|
ui_interact(user)
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
|
|
return .
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "ChameleonCard", name)
|
|
ui.open()
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_static_data(mob/user)
|
|
var/list/data = list()
|
|
data["wildcardFlags"] = SSid_access.wildcard_flags_by_wildcard
|
|
data["accessFlagNames"] = SSid_access.access_flag_string_by_flag
|
|
data["accessFlags"] = SSid_access.flags_by_access
|
|
return data
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_host(mob/user)
|
|
// Hook our UI to the theft target ID card for UI state checks.
|
|
return theft_target?.resolve()
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_state(mob/user)
|
|
return GLOB.always_state
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_status(mob/user, datum/ui_state/state)
|
|
var/target = theft_target?.resolve()
|
|
|
|
if(!target)
|
|
return UI_CLOSE
|
|
|
|
var/status = min(
|
|
ui_status_user_strictly_adjacent(user, target),
|
|
ui_status_user_is_advanced_tool_user(user),
|
|
max(
|
|
ui_status_user_is_conscious_and_lying_down(user),
|
|
ui_status_user_is_abled(user, target),
|
|
),
|
|
)
|
|
|
|
if(status < UI_INTERACTIVE)
|
|
return UI_CLOSE
|
|
|
|
return status
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_data(mob/user)
|
|
var/list/data = list()
|
|
|
|
data["showBasic"] = FALSE
|
|
|
|
var/list/regions = list()
|
|
|
|
var/obj/item/card/id/target_card = theft_target.resolve()
|
|
if(target_card)
|
|
var/list/tgui_region_data = SSid_access.all_region_access_tgui
|
|
for(var/region in SSid_access.station_regions)
|
|
regions += tgui_region_data[region]
|
|
|
|
data["accesses"] = regions
|
|
data["ourAccess"] = access
|
|
data["ourTrimAccess"] = trim ? trim.access : list()
|
|
data["theftAccess"] = target_card.access.Copy()
|
|
data["wildcardSlots"] = wildcard_slots
|
|
data["selectedList"] = access
|
|
data["trimAccess"] = list()
|
|
|
|
return data
|
|
|
|
/obj/item/card/id/advanced/chameleon/ui_act(action, list/params)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
|
|
var/obj/item/card/id/target_card = theft_target?.resolve()
|
|
if(QDELETED(target_card))
|
|
to_chat(usr, span_notice("The ID card you were attempting to scan is no longer in range."))
|
|
target_card = null
|
|
return TRUE
|
|
|
|
// Wireless ID theft!
|
|
var/turf/our_turf = get_turf(src)
|
|
var/turf/target_turf = get_turf(target_card)
|
|
if(!our_turf.Adjacent(target_turf))
|
|
to_chat(usr, span_notice("The ID card you were attempting to scan is no longer in range."))
|
|
target_card = null
|
|
return TRUE
|
|
|
|
switch(action)
|
|
if("mod_access")
|
|
var/access_type = params["access_target"]
|
|
var/try_wildcard = params["access_wildcard"]
|
|
if(access_type in access)
|
|
remove_access(list(access_type))
|
|
LOG_ID_ACCESS_CHANGE(usr, src, "removed [SSid_access.get_access_desc(access_type)]")
|
|
return TRUE
|
|
|
|
if(!(access_type in target_card.access))
|
|
to_chat(usr, span_notice("ID error: ID card rejected your attempted access modification."))
|
|
LOG_ID_ACCESS_CHANGE(usr, src, "failed to add [SSid_access.get_access_desc(access_type)][try_wildcard ? " with wildcard [try_wildcard]" : ""]")
|
|
return TRUE
|
|
|
|
if(!can_add_wildcards(list(access_type), try_wildcard))
|
|
to_chat(usr, span_notice("ID error: ID card rejected your attempted access modification."))
|
|
LOG_ID_ACCESS_CHANGE(usr, src, "failed to add [SSid_access.get_access_desc(access_type)][try_wildcard ? " with wildcard [try_wildcard]" : ""]")
|
|
return TRUE
|
|
|
|
if(!add_access(list(access_type), try_wildcard))
|
|
to_chat(usr, span_notice("ID error: ID card rejected your attempted access modification."))
|
|
LOG_ID_ACCESS_CHANGE(usr, src, "failed to add [SSid_access.get_access_desc(access_type)][try_wildcard ? " with wildcard [try_wildcard]" : ""]")
|
|
return TRUE
|
|
|
|
if(access_type in ACCESS_ALERT_ADMINS)
|
|
message_admins("[ADMIN_LOOKUPFLW(usr)] just added [SSid_access.get_access_desc(access_type)] to an ID card [ADMIN_VV(src)] [(registered_name) ? "belonging to [registered_name]." : "with no registered name."]")
|
|
LOG_ID_ACCESS_CHANGE(usr, src, "added [SSid_access.get_access_desc(access_type)]")
|
|
return TRUE
|
|
|
|
/obj/item/card/id/advanced/chameleon/attack_self(mob/user)
|
|
if(!user.can_perform_action(user, NEED_DEXTERITY| FORBID_TELEKINESIS_REACH))
|
|
return ..()
|
|
var/popup_input = tgui_input_list(user, "Choose Action", "Agent ID", list("Show", "Forge/Reset", "Change Account ID"))
|
|
if(!popup_input || !after_input_check(user))
|
|
return TRUE
|
|
switch(popup_input)
|
|
if ("Change Account ID")
|
|
set_new_account(user)
|
|
return
|
|
if("Show")
|
|
return ..()
|
|
|
|
///"Forge/Reset", kept outside the switch() statement to reduce indentation.
|
|
if(forged) //reset the ID if forged
|
|
registered_name = initial(registered_name)
|
|
assignment = initial(assignment)
|
|
SSid_access.remove_trim_from_chameleon_card(src)
|
|
REMOVE_TRAIT(src, TRAIT_MAGNETIC_ID_CARD, CHAMELEON_ITEM_TRAIT)
|
|
user.log_message("reset \the [initial(name)] named \"[src]\" to default.", LOG_GAME)
|
|
update_label()
|
|
update_icon()
|
|
forged = FALSE
|
|
to_chat(user, span_notice("You successfully reset the ID card."))
|
|
return
|
|
|
|
///forge the ID if not forged.
|
|
var/input_name = tgui_input_text(user, "What name would you like to put on this card? Leave blank to randomise.", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name), MAX_NAME_LEN)
|
|
if(!after_input_check(user))
|
|
return TRUE
|
|
if(input_name)
|
|
input_name = sanitize_name(input_name, allow_numbers = TRUE)
|
|
if(!input_name)
|
|
// Invalid/blank names give a randomly generated one.
|
|
if(user.gender == MALE)
|
|
input_name = "[pick(GLOB.first_names_male)] [pick(GLOB.last_names)]"
|
|
else if(user.gender == FEMALE)
|
|
input_name = "[pick(GLOB.first_names_female)] [pick(GLOB.last_names)]"
|
|
else
|
|
input_name = "[pick(GLOB.first_names)] [pick(GLOB.last_names)]"
|
|
|
|
var/change_trim = tgui_alert(user, "Adjust the appearance of your card's trim?", "Modify Trim", list("Yes", "No"))
|
|
if(!after_input_check(user))
|
|
return TRUE
|
|
var/selected_trim_path
|
|
var/static/list/trim_list
|
|
if(change_trim == "Yes")
|
|
trim_list = list()
|
|
for(var/trim_path in typesof(/datum/id_trim))
|
|
var/datum/id_trim/trim = SSid_access.trim_singletons_by_path[trim_path]
|
|
if(trim && trim.trim_state && trim.assignment)
|
|
var/fake_trim_name = "[trim.assignment] ([trim.trim_state])"
|
|
trim_list[fake_trim_name] = trim_path
|
|
selected_trim_path = tgui_input_list(user, "Select trim to apply to your card.\nNote: This will not grant any trim accesses.", "Forge Trim", sort_list(trim_list, GLOBAL_PROC_REF(cmp_typepaths_asc)))
|
|
if(!after_input_check(user))
|
|
return TRUE
|
|
|
|
var/target_occupation = tgui_input_text(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels.", "Agent card job assignment", assignment ? assignment : "Assistant", MAX_NAME_LEN)
|
|
if(!after_input_check(user))
|
|
return TRUE
|
|
|
|
var/new_age = tgui_input_number(user, "Choose the ID's age", "Agent card age", AGE_MIN, AGE_MAX, AGE_MIN)
|
|
if(!after_input_check(user))
|
|
return TRUE
|
|
|
|
var/wallet_spoofing = tgui_alert(user, "Activate wallet ID spoofing, allowing this card to force itself to occupy the visible ID slot in wallets?", "Wallet ID Spoofing", list("Yes", "No"))
|
|
if(!after_input_check(user))
|
|
return
|
|
|
|
registered_name = input_name
|
|
if(selected_trim_path)
|
|
SSid_access.apply_trim_to_chameleon_card(src, trim_list[selected_trim_path])
|
|
if(target_occupation)
|
|
assignment = sanitize(target_occupation)
|
|
if(new_age)
|
|
registered_age = new_age
|
|
if(wallet_spoofing == "Yes")
|
|
ADD_TRAIT(src, TRAIT_MAGNETIC_ID_CARD, CHAMELEON_ITEM_TRAIT)
|
|
|
|
update_label()
|
|
update_icon()
|
|
forged = TRUE
|
|
to_chat(user, span_notice("You successfully forge the ID card."))
|
|
user.log_message("forged \the [initial(name)] with name \"[registered_name]\", occupation \"[assignment]\" and trim \"[trim?.assignment]\".", LOG_GAME)
|
|
|
|
if(!registered_account && ishuman(user))
|
|
var/mob/living/carbon/human/accountowner = user
|
|
|
|
var/datum/bank_account/account = SSeconomy.bank_accounts_by_id["[accountowner.account_id]"]
|
|
if(account)
|
|
account.bank_cards += src
|
|
registered_account = account
|
|
to_chat(user, span_notice("Your account number has been automatically assigned."))
|
|
|
|
/obj/item/card/id/advanced/chameleon/proc/after_input_check(mob/user)
|
|
if(QDELETED(user) || QDELETED(src) || !user.client || !user.can_perform_action(src, NEED_DEXTERITY|FORBID_TELEKINESIS_REACH))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/card/id/advanced/chameleon/add_item_context(obj/item/source, list/context, atom/target, mob/living/user,)
|
|
. = ..()
|
|
|
|
if(!in_range(user, target))
|
|
return .
|
|
if(ishuman(target))
|
|
context[SCREENTIP_CONTEXT_RMB] = "Copy access"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
if(isitem(target))
|
|
context[SCREENTIP_CONTEXT_RMB] = "Scan for access"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
return .
|
|
|
|
/// A special variant of the classic chameleon ID card which accepts all access.
|
|
/obj/item/card/id/advanced/chameleon/black
|
|
icon_state = "card_black"
|
|
assigned_icon_state = "assigned_syndicate"
|
|
wildcard_slots = WILDCARD_LIMIT_GOLD
|
|
|
|
/obj/item/card/id/advanced/engioutpost
|
|
registered_name = "George 'Plastic' Miller"
|
|
desc = "A card used to provide ID and determine access across the station. There's blood dripping from the corner. Ew."
|
|
trim = /datum/id_trim/engioutpost
|
|
registered_age = 47
|
|
|
|
/obj/item/card/id/advanced/simple_bot
|
|
name = "simple bot ID card"
|
|
desc = "An internal ID card used by the station's non-sentient bots. You should report this to a coder if you're holding it."
|
|
wildcard_slots = WILDCARD_LIMIT_ADMIN
|
|
|
|
/obj/item/card/id/red
|
|
name = "Red Team identification card"
|
|
desc = "A card used to identify members of the red team for CTF"
|
|
icon_state = "ctf_red"
|
|
|
|
/obj/item/card/id/blue
|
|
name = "Blue Team identification card"
|
|
desc = "A card used to identify members of the blue team for CTF"
|
|
icon_state = "ctf_blue"
|
|
|
|
/obj/item/card/id/yellow
|
|
name = "Yellow Team identification card"
|
|
desc = "A card used to identify members of the yellow team for CTF"
|
|
icon_state = "ctf_yellow"
|
|
|
|
/obj/item/card/id/green
|
|
name = "Green Team identification card"
|
|
desc = "A card used to identify members of the green team for CTF"
|
|
icon_state = "ctf_green"
|
|
|
|
#undef INTERN_THRESHOLD_FALLBACK_HOURS
|
|
#undef HOLOPAY_PROJECTION_INTERVAL
|
|
|
|
#define INDEX_NAME_COLOR 1
|
|
#define INDEX_ASSIGNMENT_COLOR 2
|
|
#define INDEX_TRIM_COLOR 3
|
|
|
|
/**
|
|
* A fake ID card any silly-willy can craft with wirecutters, cardboard and a writing utensil
|
|
* Beside the gimmick of changing the visible name when worn, they do nothing. They cannot have an account.
|
|
* They don't fit in PDAs nor wallets, They have no access. They won't trick securitrons. They won't work with chameleon masks.
|
|
* Etcetera etcetera. Furthermore, talking, or getting examined on will pretty much give it away.
|
|
*/
|
|
/obj/item/card/cardboard
|
|
name = "cardboard identification card"
|
|
desc = "A card used to provide ID and det- Heeeey, wait a second, this is just a piece of cut cardboard!"
|
|
icon_state = "cardboard_id"
|
|
inhand_icon_state = "cardboard-id"
|
|
worn_icon_state = "nothing"
|
|
resistance_flags = FLAMMABLE
|
|
slot_flags = ITEM_SLOT_ID
|
|
///The "name" of the "owner" of this "ID"
|
|
var/scribbled_name
|
|
///The assignment written on this card.
|
|
var/scribbled_assignment
|
|
///An icon state used as trim.
|
|
var/scribbled_trim
|
|
///The colors for each of the above variables, for when overlays are updated.
|
|
var/details_colors = list(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK)
|
|
|
|
/obj/item/card/cardboard/equipped(mob/user, slot, initial = FALSE)
|
|
. = ..()
|
|
if(slot == ITEM_SLOT_ID)
|
|
RegisterSignal(user, COMSIG_HUMAN_GET_VISIBLE_NAME, PROC_REF(return_visible_name))
|
|
RegisterSignal(user, COMSIG_MOVABLE_MESSAGE_GET_NAME_PART, PROC_REF(return_message_name_part))
|
|
|
|
/obj/item/card/cardboard/dropped(mob/user, silent = FALSE)
|
|
. = ..()
|
|
UnregisterSignal(user, list(COMSIG_HUMAN_GET_VISIBLE_NAME, COMSIG_MOVABLE_MESSAGE_GET_NAME_PART))
|
|
|
|
/obj/item/card/cardboard/proc/return_visible_name(mob/living/carbon/human/source, list/identity)
|
|
SIGNAL_HANDLER
|
|
identity[VISIBLE_NAME_ID] = scribbled_name
|
|
|
|
/obj/item/card/cardboard/proc/return_message_name_part(mob/living/carbon/human/source, list/stored_name, visible_name)
|
|
SIGNAL_HANDLER
|
|
if(visible_name)
|
|
return
|
|
var/voice_name = source.GetVoice()
|
|
if(source.name != voice_name)
|
|
voice_name += " (as [scribbled_name])"
|
|
stored_name[NAME_PART_INDEX] = voice_name
|
|
|
|
/obj/item/card/cardboard/attackby(obj/item/item, mob/living/user, params)
|
|
if(user.can_write(item, TRUE))
|
|
INVOKE_ASYNC(src, PROC_REF(modify_card), user, item)
|
|
return TRUE
|
|
return ..()
|
|
|
|
///Lets the user write a name, assignment or trim on the card, or reset it. Only the name is important for the component.
|
|
/obj/item/card/cardboard/proc/modify_card(mob/living/user, obj/item/item)
|
|
if(!user.mind)
|
|
return
|
|
var/popup_input = tgui_input_list(user, "What To Change", "Cardboard ID", list("Name", "Assignment", "Trim", "Reset"))
|
|
if(!after_input_check(user, item, popup_input))
|
|
return
|
|
switch(popup_input)
|
|
if("Name")
|
|
var/input_name = tgui_input_text(user, "What name would you like to put on this card?", "Cardboard card name", scribbled_name || (ishuman(user) ? user.real_name : user.name), MAX_NAME_LEN)
|
|
input_name = sanitize_name(input_name, allow_numbers = TRUE)
|
|
if(!after_input_check(user, item, input_name, scribbled_name))
|
|
return
|
|
scribbled_name = input_name
|
|
var/list/details = item.get_writing_implement_details()
|
|
details_colors[INDEX_NAME_COLOR] = details["color"] || COLOR_BLACK
|
|
if("Assignment")
|
|
var/input_assignment = tgui_input_text(user, "What assignment would you like to put on this card?", "Cardboard card job ssignment", scribbled_assignment || "Assistant", MAX_NAME_LEN)
|
|
if(!after_input_check(user, item, input_assignment, scribbled_assignment))
|
|
return
|
|
scribbled_assignment = sanitize(input_assignment)
|
|
var/list/details = item.get_writing_implement_details()
|
|
details_colors[INDEX_ASSIGNMENT_COLOR] = details["color"] || COLOR_BLACK
|
|
if("Trim")
|
|
var/static/list/possible_trims
|
|
if(!possible_trims)
|
|
possible_trims = list()
|
|
for(var/trim_path in typesof(/datum/id_trim))
|
|
var/datum/id_trim/trim = SSid_access.trim_singletons_by_path[trim_path]
|
|
if(trim?.trim_state && trim.assignment)
|
|
possible_trims |= replacetext(trim.trim_state, "trim_", "")
|
|
sortTim(possible_trims, GLOBAL_PROC_REF(cmp_typepaths_asc))
|
|
var/input_trim = tgui_input_list(user, "Select trim to apply to your card.\nNote: This will not grant any trim accesses.", "Forge Trim", possible_trims)
|
|
if(!input_trim || !after_input_check(user, item, input_trim, scribbled_trim))
|
|
return
|
|
scribbled_trim = "cardboard_[input_trim]"
|
|
var/list/details = item.get_writing_implement_details()
|
|
details_colors[INDEX_TRIM_COLOR] = details["color"] || COLOR_BLACK
|
|
if("Reset")
|
|
scribbled_name = null
|
|
scribbled_assignment = null
|
|
scribbled_trim = null
|
|
details_colors = list(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK)
|
|
|
|
update_appearance()
|
|
|
|
///Checks that the conditions to be able to modify the cardboard card are still present after user input calls.
|
|
/obj/item/card/cardboard/proc/after_input_check(mob/living/user, obj/item/item, input, value)
|
|
if(!input || (value && input == value))
|
|
return FALSE
|
|
if(QDELETED(user) || QDELETED(item) || QDELETED(src) || user.incapacitated() || !user.is_holding(item) || !user.CanReach(src) || !user.can_write(item))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/obj/item/card/cardboard/attack_self(mob/user)
|
|
if(!Adjacent(user))
|
|
return
|
|
user.visible_message(span_notice("[user] shows you: [icon2html(src, viewers(user))] [name]."), span_notice("You show \the [name]."))
|
|
add_fingerprint(user)
|
|
|
|
/obj/item/card/cardboard/update_name()
|
|
. = ..()
|
|
if(!scribbled_name)
|
|
name = initial(name)
|
|
return
|
|
name = "[scribbled_name]'s ID Card ([scribbled_assignment])"
|
|
|
|
/obj/item/card/cardboard/update_overlays()
|
|
. = ..()
|
|
if(scribbled_name)
|
|
var/mutable_appearance/name_overlay = mutable_appearance(icon, "cardboard_name")
|
|
name_overlay.color = details_colors[INDEX_NAME_COLOR]
|
|
. += name_overlay
|
|
if(scribbled_assignment)
|
|
var/mutable_appearance/assignment_overlay = mutable_appearance(icon, "cardboard_assignment")
|
|
assignment_overlay.color = details_colors[INDEX_ASSIGNMENT_COLOR]
|
|
. += assignment_overlay
|
|
if(scribbled_trim)
|
|
var/mutable_appearance/frame_overlay = mutable_appearance(icon, "cardboard_frame")
|
|
frame_overlay.color = details_colors[INDEX_TRIM_COLOR]
|
|
. += frame_overlay
|
|
var/mutable_appearance/trim_overlay = mutable_appearance(icon, scribbled_trim)
|
|
trim_overlay.color = details_colors[INDEX_TRIM_COLOR]
|
|
. += trim_overlay
|
|
|
|
/obj/item/card/cardboard/get_id_examine_strings(mob/user)
|
|
. = ..()
|
|
. += list("[icon2html(get_cached_flat_icon(), user, extra_classes = "bigicon")]")
|
|
|
|
/obj/item/card/cardboard/get_examine_string(mob/user, thats = FALSE)
|
|
return "[icon2html(get_cached_flat_icon(), user)] [thats? "That's ":""][get_examine_name(user)]"
|
|
|
|
/obj/item/card/cardboard/examine(mob/user)
|
|
. = ..()
|
|
. += span_notice("You could use a pen or crayon to forge a name, assignment or trim.")
|
|
|
|
#undef INDEX_NAME_COLOR
|
|
#undef INDEX_ASSIGNMENT_COLOR
|
|
#undef INDEX_TRIM_COLOR
|